前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Sentry 开发者贡献指南 - 前端 React Hooks 与虫洞状态管理模式

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

作者头像
为少
发布2021-12-20 20:38:50
6640
发布2021-12-20 20:38:50
举报
文章被收录于专栏:黑客下午茶黑客下午茶

系列

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

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

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

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

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

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

一个例子

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

CodeSandbox(示例代码)

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

步骤 1

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

代码语言:javascript
复制
const ClickCounter = () => {
  const [count, setCount] = useState(0);

  function onClick() {
    setCount(count => count + 1);
  }

  return <button onClick={onClick}>{count} +1</button>;
};

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

足够简单。

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

步骤 2

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

状态保留在 ClickCounter 组件中。

代码语言:javascript
复制
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 个属于一起的项。

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

代码语言:javascript
复制
const ClickCounter = () => {
  const [count, setCount] = useState({ A: 0, B: 0 });

  function onClickA() {
    setCount(count => {
      return { ...count, A: count.A + 1 };
    });
  }

  function onClickB() {
    setCount(count => {
      return { ...count, B: count.B + 1 };
    });
  }

  return (
    <>
      <p>
        You have clicked buttons A: {count.A}, B: {count.B} times
      </p>
      <div style={{ textAlign: "right" }}>
        <PrettyButton onClick={onClickA}>A +1</PrettyButton>
        &nbsp;
        <PrettyButton onClick={onClickB}>B +1</PrettyButton>
      </div>
    </>
  );
};

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

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

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

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

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

代码语言:javascript
复制
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 个按钮更新相同的状态怎么办?

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

代码语言:javascript
复制
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>It's {count.B} btw</p>
    </div>
  );
};

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

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

步骤 5

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

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

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

这是这部分的工作原理。

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

代码语言:javascript
复制
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 是一个更丰富的状态,它也包含操作该状态所需的一切。通常,这将是来自您的 reducerdispatch 方法,或者像我们这里的自定义状态设置器。

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

代码语言:javascript
复制
setSharedCount("B", 10);

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

使 React 树更稳定 ✌️

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

代码语言:javascript
复制
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 共享状态,定义更简单的 incAincB 辅助方法,并返回它们的状态。

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

代码语言:javascript
复制
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>It's {count.B} btw</p>
    </div>
  );
};

从自定义 hook 获取 countincB。使用它们。

性能怎么样?

很好。

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

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

复杂度如何?

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

讨厌管理自己的状态

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

代码语言:javascript
复制
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.tsx

Refs

  • Wormhole state management
    • https://swizec.com/blog/wormhole-state-management/
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-12-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 黑客下午茶 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 系列
  • 什么是虫洞状态管理模式?
  • 一个例子
    • 步骤 1
      • 步骤 2
        • 步骤 3
          • 步骤 4
            • 步骤 5
            • 性能怎么样?
            • 复杂度如何?
            • 讨厌管理自己的状态
            • 顶级开源项目是如何使用的?(Sentry)
            • Refs
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档