Sentry 开发者贡献指南-前端 React Hooks 与虫洞状态管理模式
什么是南前虫洞状态管理模式?
您可以逃脱的最小 state 共享量是多少?
保持你的 state。尽可能靠近使用它的端R洞状地方。
如果有一个组件关心这个问题,态管使用它。理模如果有几个组件在意,南前就用 props 分享一下。端R洞状如果很多组件都关心,态管把它放在 context 中。理模
Context 就像一个虫洞。南前它使您的端R洞状组件树弯曲,因此相距很远的态管部分可以接触。
利用自定义 hooks 使这变得容易。理模
一个例子
构建一个点击计数器。南前虫洞状态管理模式最好通过示例来解释 ?端R洞状?
CodeSandbox(示例代码)
https://codesandbox.io/s/wormhole-state-pattern-5-j4w5e?file=/src/App.js步骤 1
我们从 useState 开始,因为它是态管最简单的。
count 保存当前的点击次数,setCount 让我们在每次点击时更新值。
足够简单。
不过,外观并不是很漂亮。让我们用一个自定义按钮组件和一些嵌套来改进它。
步骤 2
我们创建了一个可重复使用的 PrettyButton,云南idc服务商确保您应用中的每个按钮看起来都很棒。
状态保留在 ClickCounter 组件中。
const ClickCounter = () => { const [count, setCount] = useState(0); function onClick() { setCount(count => count + 1); } return ( <> <p>You have clicked buttons { count} times</p> <div style={ { textAlign: "right" }}> <PrettyButton onClick={ onClick}>+1</PrettyButton> </div> </> ); };这是必要的最少状态共享。我们也保持了简单的状态。
计数器组件关心点击次数和计数,因此它将回调作为 props 传递到按钮中。函数被调用,状态更新,组件重新渲染。
不需要复杂的操作。
步骤 3
如果我们的状态更复杂怎么办?我们有 2 个属于一起的项。
您可以在您的状态中保留复杂的值。效果很好。
我们已将 count 拆分为一个对象 – { A, B }。
现在单个状态可以保存多个值。单独按钮点击的单独计数。
React 使用 JavaScript 相等来检测重新渲染的更改,因此您必须在每次更新时制作完整状态的副本。这在大约 10,000 个元素时变慢。
您也可以在这里使用 useReducer。特别是服务器托管当您的状态变得更加复杂并且项目经常单独更新时。
使用 useReducer 的类似状态如下所示:
const [state, dispatch] = useReducer((action, state) => { switch (action.type) { case A: return { ...state, A: state.A + 1 } case B: return { ...state, A: state.A + 1 } } }, { A: 0, B: 0}) function onClickA() { dispatch({ type: A }) }你的状态越复杂,这就越有意义。
但我认为那些 switch 语句很快就会变得混乱,而且你的回调函数无论如何都已经是动作了。
步骤 4
如果我们想要 2 个按钮更新相同的状态怎么办?
您可以将 count 和 setCount 作为 props 传递给您的组件。但这变得越来越混乱。
const AlternativeClick = ({ count, setCount }) => { function onClick() { setCount(count => { return { ...count, B: count.B + 1 }; }); } return ( <div style={ { textAlign: "left" }}> You can also update B here <br /> <PrettyButton onClick={ onClick}>B +1</PrettyButton> <p>Its { count.B} btw</p> </div> ); };我们创建了一个难以移动并且需要理解太多父逻辑的组件。关注点是分裂的,抽象是奇怪的,我们造成了混乱。
你可以通过只传递它需要的状态部分和一个更自定义的 setCount 来修复它。但这是很多工作。
步骤 5
相反,您可以使用虫洞与自定义 hook 共享状态。
您现在有 2 个共享状态的独立组件。将它们放在您的亿华云代码库中的任何位置,它 Just Works?。
需要在其他地方访问共享状态?添加 useSharedCount hook,瞧。
这是这部分的工作原理。
我们有一个 context provider,里面有一些操作:
export const SharedCountProvider = ({ children }) => { // replace with useReducer for more flexiblity const [state, setState] = useState(defaultState); const [contextValue, setContextValue] = useState({ state, // dispatch // from your reducer // this is where a reducer comes handy when this grows setSharedCount: (key, val) => { setState(state => { return { ...state, [key]: val }; }); } // other stuff you need in context }); // avoids deep re-renders // when instances of stuff in context change useEffect(() => { setContextValue(currentValue => ({ ...currentValue, state })); }, [state]); return ( <SharedCountContext.Provider value={ contextValue}> { children} </SharedCountContext.Provider> ); };Context Provider 使用丰富的 state 变量来保持您的状态。这里对我们来说是 { A, B }。
contextValue 是一个更丰富的状态,它也包含操作该状态所需的一切。通常,这将是来自您的 reducer 的 dispatch 方法,或者像我们这里的自定义状态设置器。
我们的 setSharedCount 方法获取一个 key 和一个 val 并更新该部分状态。
setSharedCount("B", 10);然后我们有一个副作用,它观察 state 的变化并在需要时触发重新渲染。这避免了每次我们重新定义我们的 dispatch 方法或其他任何东西时的深度重新渲染。
使 React 树更稳定 ??
在这个 provider 中呈现的每个组件都可以使用这个相同的自定义 hook 来访问它需要的一切。
export function useSharedCount() { const { state, setSharedCount } = useContext(SharedCountContext); function incA() { setSharedCount("A", state.A + 1); } function incB() { setSharedCount("B", state.B + 1); } return { count: state, incA, incB }; }自定义 hook 利用 React Context 共享状态,定义更简单的 incA 和 incB 辅助方法,并返回它们的状态。
这意味着我们的 AlternativeClick 组件可以是这样的:
import { useSharedCount } from "./SharedCountContextProvider"; const AlternativeClick = () => { const { count, incB } = useSharedCount(); return ( <div style={ { textAlign: "left" }}> You can also update B here <br /> <PrettyButton onClick={ incB}>B +1</PrettyButton> <p>Its { count.B} btw</p> </div> ); };从自定义 hook 获取 count 和 incB。使用它们。
性能怎么样?
很好。
尽可能少地共享 state。对应用程序的不同部分使用不同的 context provider。
不要让它成为 global,除非它需要是 global 的。包裹你可以逃脱的树的最小部分。
复杂度如何?
什么复杂度?保持小。不要把你不需要的东西塞进去。
讨厌管理自己的状态
看到我们 SharedCountProvider 中处理状态变化的部分了吗?这部分:
const [contextValue, setContextValue] = useState({ state, // dispatch // from your reducer // this is where a reducer comes handy when this grows setSharedCount: (key, val) => { setState(state => { return { ...state, [key]: val }; }); } // other stuff you need in context });为此,您可以使用 XState。或者 reducer。甚至 Redux,如果你真的想要的话。
不过,如果你使用 Redux,你不妨一路走下去 ??
顶级开源项目是如何使用的?(Sentry)
organizationContext.tsx(详细代码)
https://github.com/getsentry/sentry/blob/master/static/app/views/organizationContext.tsxRefs
Wormhole state management
https://swizec.com/blog/wormhole-state-management/