在 React Context 中与全局状态交互时过度重新渲染

问题描述 投票:0回答:4

我正在构建一个聊天应用程序,我正在使用 ContextAPI 来保存我需要从不同的不相关组件访问的状态。

由于上下文而发生了很多重新渲染,每次我在输入中键入一个字母时,所有组件都会重新渲染,当我切换

RightBar
时也是如此,它的状态也驻留在上下文中,因为我需要从按钮切换它在
Navbar

我尝试在每个组件上使用

memo
,但每次我与任何组件的上下文中的状态交互时,所有组件仍然会重新渲染。

我将简化的整个代码添加到此沙箱链接:https://codesandbox.io/s/interesting-sky-fzmc6

这是已部署的 Netlify 链接:https://csb-fzmc6.netlify.app/

我尝试将我的代码分成一些自定义钩子,例如

useChatSerice
useUsersService
来简化代码并使实际组件干净,我也将感谢任何有关如何更好地构造这些钩子以及在何处放置 CRUD 函数的见解同时避免过度重新渲染。

我发现一些“解决方案”表明使用多个上下文应该有所帮助,但我不知道在我的具体情况下如何做到这一点,已经被这个问题困扰了一个星期。

编辑:

  • 这里的主要问题是对输入中输入的每个字母进行完全重新渲染。
  • 第二个是
    RightBar
    切换按钮,它也会导致完全重新渲染。
reactjs react-hooks react-context
4个回答
2
投票

将导航栏和聊天状态拆分为两个单独的 React 上下文实际上是 React 推荐的方法。通过将所有状态嵌套到新的对象引用中,只要任何单个状态更新,它必然会触发所有消费者的重新渲染。

<ChatContext.Provider
  value={{ // <-- new object reference each render
    rightBarValue: [rightBarIsOpen, setRightBarIsOpen],
    chatState: {
      editValue,
      setEditValue,
      editingId,
      setEditingId,
      inputValue,
      setInputValue,
    },
  }}
>
  {children}
</ChatContext.Provider>

我建议将

rightBarValue
和状态设置器雕刻到自己的上下文中。

导航栏上下文

const NavBarContext = createContext([false, () => {}]);

const NavBarProvider = ({ children }) => {
  const [rightBarIsOpen, setRightBarIsOpen] = useState(true);
  return (
    <NavBarContext.Provider value={[rightBarIsOpen, setRightBarIsOpen]}>
      {children}
    </NavBarContext.Provider>
  );
};

const useNavBar = () => useContext(NavBarContext);

聊天上下文

const ChatContext = createContext({
  editValue: "",
  setEditValue: () => {},
  editingId: null,
  setEditingId: () => {},
  inputValue: "",
  setInputValue: () => {}
});

const ChatProvider = ({ children }) => {
  const [inputValue, setInputValue] = useState("");
  const [editValue, setEditValue] = useState("");
  const [editingId, setEditingId] = useState(null);

  const chatState = useMemo(
    () => ({
      editValue,
      setEditValue,
      editingId,
      setEditingId,
      inputValue,
      setInputValue
    }),
    [editValue, inputValue, editingId]
  );

  return (
    <ChatContext.Provider value={chatState}>{children}</ChatContext.Provider>
  );
};

const useChat = () => {
  return useContext(ChatContext);
};

主容器

const MainContainer = () => {
  return (
    <ChatProvider>
      <NavBarProvider>
        <Container>
          <NavBar />
          <ChatSection />
        </Container>
      </NavBarProvider>
    </ChatProvider>
  );
};

导航栏 - 使用

useNavBar
钩子

const NavBar = () => {
  const [rightBarIsOpen, setRightBarIsOpen] = useNavBar();

  useEffect(() => {
    console.log("NavBar rendered"); // <-- log when rendered
  });

  return (
    <NavBarContainer>
      <span>MY NAVBAR</span>
      <button onClick={() => setRightBarIsOpen(!rightBarIsOpen)}>
        TOGGLE RIGHT-BAR
      </button>
    </NavBarContainer>
  );
};

聊天

const Chat = ({ chatLines }) => {
  const { addMessage, updateMessage, deleteMessage } = useChatService();
  const {
    editValue,
    setEditValue,
    editingId,
    setEditingId,
    inputValue,
    setInputValue
  } = useChat();

  useEffect(() => {
    console.log("Chat rendered"); // <-- log when rendered
  });

  return (
    ...
  );
};

现在运行应用程序时请注意,

"NavBar rendered"
仅在切换导航栏时记录,而
"Chat rendered"
仅在聊天文本区域中键入时记录。

Edit excessive-rerendering-when-interacting-with-global-state-in-react-context


1
投票

我建议使用jotai或其他状态管理库。
上下文不适合高频变化。
并且,

RightBar
的状态外观可以与其他钩子/上下文分开。

有一个棘手的解决方案可以解决一些渲染问题: https://codesandbox.io/s/stoic-mclaren-x6yfv?file=/src/context/ChatContext.js

你的代码需要重构,

useChatService
中的
ChatSection
也依赖于你的
useChat
,所以
ChatSection
会在文本改变时重新渲染。


1
投票

看起来您正在更改输入字段数据更改的全局上下文。如果您的全局上下文是在父组件级别上定义的(相对于您的输入组件),则父组件和所有子组件都必须重新渲染。 您有多种选择可以避免这种行为:

  1. 使用较低级别的上下文,例如通过将输入字段提取到外部组件并使用 useContext 钩子
  2. 将输入保存到组件的本地状态,并且仅在模糊或提交时将其同步到全局上下文

0
投票

如果您在使用 Context API 时遇到不必要的重新渲染,切换到 useSyncExternalStore 挂钩可以解决您的问题。

这个钩子允许您以更有效的方式管理状态,防止组件在不需要时重新渲染。

我最近写了一篇关于这个主题的博客,解释了它的工作原理以及为什么它是用于全局状态管理的 Context API 的绝佳替代方案。

在这里查看: https://medium.com/@smit-khanpara/enhance-react-performance-replace-context-api-with-usesyncexternalstore-for-better-state-6af420cf7951

© www.soinside.com 2019 - 2024. All rights reserved.