Sentry 开发者贡献指南-前端 React Hooks 与虫洞状态管理模式

开发 前端
如果有一个组件关心这个问题,使用它。如果有几个组件在意,就用 props 分享一下。如果很多组件都关心,把它放在 context 中。

[[441055]]

什么是虫洞状态管理模式?

您可以逃脱的最小 state 共享量是多少?

保持你的 state。尽可能靠近使用它的地方。

如果有一个组件关心这个问题,使用它。如果有几个组件在意,就用 props 分享一下。如果很多组件都关心,把它放在 context 中。

Context 就像一个虫洞。它使您的组件树弯曲,因此相距很远的部分可以接触。

利用自定义 hooks 使这变得容易。

一个例子

构建一个点击计数器。虫洞状态管理模式最好通过示例来解释 ??

CodeSandbox(示例代码)

  • https://codesandbox.io/s/wormhole-state-pattern-5-j4w5e?file=/src/App.js

步骤 1

我们从 useState 开始,因为它是最简单的。

  1. const ClickCounter = () => { 
  2.   const [count, setCount] = useState(0); 
  3.  
  4.   function onClick() { 
  5.     setCount(count => count + 1); 
  6.   } 
  7.  
  8.   return <button onClick={onClick}>{count} +1</button>; 
  9. }; 

count 保存当前的点击次数,setCount 让我们在每次点击时更新值。

足够简单。

不过,外观并不是很漂亮。让我们用一个自定义按钮组件和一些嵌套来改进它。

步骤 2

我们创建了一个可重复使用的 PrettyButton,确保您应用中的每个按钮看起来都很棒。

状态保留在 ClickCounter 组件中。

  1. const ClickCounter = () => { 
  2.   const [count, setCount] = useState(0); 
  3.  
  4.   function onClick() { 
  5.     setCount(count => count + 1); 
  6.   } 
  7.  
  8.   return ( 
  9.     <> 
  10.       <p>You have clicked buttons {count} times</p> 
  11.       <div style={{ textAlign: "right" }}> 
  12.         <PrettyButton onClick={onClick}>+1</PrettyButton> 
  13.       </div> 
  14.     </> 
  15.   ); 
  16. }; 

这是必要的最少状态共享。我们也保持了简单的状态。

计数器组件关心点击次数和计数,因此它将回调作为 props 传递到按钮中。函数被调用,状态更新,组件重新渲染。

不需要复杂的操作。

步骤 3

如果我们的状态更复杂怎么办?我们有 2 个属于一起的项。

您可以在您的状态中保留复杂的值。效果很好。

  1. const ClickCounter = () => { 
  2.   const [count, setCount] = useState(0); 
  3.  
  4.   function onClick() { 
  5.     setCount(count => count + 1); 
  6.   } 
  7.  
  8.   return ( 
  9.     <> 
  10.       <p>You have clicked buttons {count} times</p> 
  11.       <div style={{ textAlign: "right" }}> 
  12.         <PrettyButton onClick={onClick}>+1</PrettyButton> 
  13.       </div> 
  14.     </> 
  15.   ); 
  16. }; 

我们已将 count 拆分为一个对象 – { A, B }。

现在单个状态可以保存多个值。单独按钮点击的单独计数。

React 使用 JavaScript 相等来检测重新渲染的更改,因此您必须在每次更新时制作完整状态的副本。这在大约 10,000 个元素时变慢。

您也可以在这里使用 useReducer。特别是当您的状态变得更加复杂并且项目经常单独更新时。

使用 useReducer 的类似状态如下所示:

  1. const [state, dispatch] = useReducer((action, state) => { 
  2.     switch (action.type) { 
  3.         case 'A'
  4.             return { ...state, A: state.A + 1 } 
  5.         case 'B'
  6.             return { ...state, A: state.A + 1 } 
  7.     } 
  8. }, { A: 0, B: 0}) 
  9.  
  10. function onClickA() { 
  11.     dispatch({ type: 'A' }) 

你的状态越复杂,这就越有意义。

但我认为那些 switch 语句很快就会变得混乱,而且你的回调函数无论如何都已经是动作了。

步骤 4

如果我们想要 2 个按钮更新相同的状态怎么办?

您可以将 count 和 setCount 作为 props 传递给您的组件。但这变得越来越混乱。

  1. const AlternativeClick = ({ count, setCount }) => { 
  2.   function onClick() { 
  3.     setCount(count => { 
  4.       return { ...count, B: count.B + 1 }; 
  5.     }); 
  6.   } 
  7.  
  8.   return ( 
  9.     <div style={{ textAlign: "left" }}> 
  10.       You can also update B here 
  11.       <br /> 
  12.       <PrettyButton onClick={onClick}>B +1</PrettyButton> 
  13.       <p>It's {count.B} btw</p> 
  14.     </div> 
  15.   ); 
  16. }; 

我们创建了一个难以移动并且需要理解太多父逻辑的组件。关注点是分裂的,抽象是奇怪的,我们造成了混乱。

你可以通过只传递它需要的状态部分和一个更自定义的 setCount 来修复它。但这是很多工作。

步骤 5

相反,您可以使用虫洞与自定义 hook 共享状态。

您现在有 2 个共享状态的独立组件。将它们放在您的代码库中的任何位置,它 Just Works?。

需要在其他地方访问共享状态?添加 useSharedCount hook,瞧。

这是这部分的工作原理。

我们有一个 context provider,里面有一些操作:

  1. export const SharedCountProvider = ({ children }) => { 
  2.   // replace with useReducer for more flexiblity 
  3.   const [state, setState] = useState(defaultState); 
  4.  
  5.   const [contextValue, setContextValue] = useState({ 
  6.     state, 
  7.     // dispatch // from your reducer 
  8.     // this is where a reducer comes handy when this grows 
  9.     setSharedCount: (key, val) => { 
  10.       setState(state => { 
  11.         return { ...state, [key]: val }; 
  12.       }); 
  13.     } 
  14.     // other stuff you need in context 
  15.   }); 
  16.  
  17.   // avoids deep re-renders 
  18.   // when instances of stuff in context change 
  19.   useEffect(() => { 
  20.     setContextValue(currentValue => ({ 
  21.       ...currentValue, 
  22.       state 
  23.     })); 
  24.   }, [state]); 
  25.  
  26.   return ( 
  27.     <SharedCountContext.Provider value={contextValue}> 
  28.       {children} 
  29.     </SharedCountContext.Provider> 
  30.   ); 
  31. }; 

Context Provider 使用丰富的 state 变量来保持您的状态。这里对我们来说是 { A, B }。

contextValue 是一个更丰富的状态,它也包含操作该状态所需的一切。通常,这将是来自您的 reducer 的 dispatch 方法,或者像我们这里的自定义状态设置器。

我们的 setSharedCount 方法获取一个 key 和一个 val 并更新该部分状态。

  1. setSharedCount("B", 10); 

然后我们有一个副作用,它观察 state 的变化并在需要时触发重新渲染。这避免了每次我们重新定义我们的 dispatch 方法或其他任何东西时的深度重新渲染。

使 React 树更稳定 ??

在这个 provider 中呈现的每个组件都可以使用这个相同的自定义 hook 来访问它需要的一切。

  1. export function useSharedCount() { 
  2.   const { state, setSharedCount } = useContext(SharedCountContext); 
  3.  
  4.   function incA() { 
  5.     setSharedCount("A", state.A + 1); 
  6.   } 
  7.  
  8.   function incB() { 
  9.     setSharedCount("B", state.B + 1); 
  10.   } 
  11.  
  12.   return { count: state, incA, incB }; 

自定义 hook 利用 React Context 共享状态,定义更简单的 incA 和 incB 辅助方法,并返回它们的状态。

这意味着我们的 AlternativeClick 组件可以是这样的:

  1. import { 
  2.   useSharedCount 
  3. from "./SharedCountContextProvider"
  4.  
  5. const AlternativeClick = () => { 
  6.   const { count, incB } = useSharedCount(); 
  7.  
  8.   return ( 
  9.     <div style={{ textAlign: "left" }}> 
  10.       You can also update B here 
  11.       <br /> 
  12.       <PrettyButton onClick={incB}>B +1</PrettyButton> 
  13.       <p>It's {count.B} btw</p> 
  14.     </div> 
  15.   ); 
  16. }; 

从自定义 hook 获取 count 和 incB。使用它们。

性能怎么样?

很好。

尽可能少地共享 state。对应用程序的不同部分使用不同的 context provider。

不要让它成为 global,除非它需要是 global 的。包裹你可以逃脱的树的最小部分。

复杂度如何?

什么复杂度?保持小。不要把你不需要的东西塞进去。

讨厌管理自己的状态

看到我们 SharedCountProvider 中处理状态变化的部分了吗?这部分:

  1. const [contextValue, setContextValue] = useState({ 
  2.     state, 
  3.     // dispatch // from your reducer 
  4.     // this is where a reducer comes handy when this grows 
  5.     setSharedCount: (key, val) => { 
  6.       setState(state => { 
  7.         return { ...state, [key]: val }; 
  8.       }); 
  9.     } 
  10.     // other stuff you need in context 
  11.   }); 

为此,您可以使用 XState。或者 reducer。甚至 Redux,如果你真的想要的话。

不过,如果你使用 Redux,你不妨一路走下去 ??

顶级开源项目是如何使用的?(Sentry)

organizationContext.tsx(详细代码)

  • https://github.com/getsentry/sentry/blob/master/static/app/views/organizationContext.tsx

Refs

Wormhole state management 

https://swizec.com/blog/wormhole-state-management/

 

责任编辑:武晓燕 来源: 黑客下午茶
相关推荐

2021-12-15 20:06:48

ReactJSSentry开发者

2022-01-11 20:42:54

开发Sentry标志

2022-01-17 19:34:43

SentryWeb APISentry API

2022-01-15 23:33:47

SentryPyCharm配置

2022-01-18 23:26:45

开发

2022-01-02 23:26:08

开发SDK Sentry

2021-12-25 22:31:55

Sentry 监控SDK 开发 性能监控

2022-01-21 21:33:03

开发JavaScript应用

2022-01-16 22:16:59

数据库Sentry开发者

2022-01-13 20:13:31

元宇宙搜索引擎

2022-01-03 22:59:30

开发SDK数据

2021-12-31 18:35:40

监控Sentry开发

2022-01-02 06:59:43

SentrySDK 开发客户端报告

2022-01-19 19:49:53

Sentry浏览器SDK

2021-12-16 20:12:37

后端开发Sentry

2022-01-20 19:49:10

Sentry开发Scope

2021-06-03 09:31:56

React状态模式

2018-06-06 14:03:19

2015-07-22 16:08:46

OpenStack开源贡献代码

2018-03-27 23:25:40

Paddle
点赞
收藏

51CTO技术栈公众号