【问题标题】:Excessive rerendering when interacting with global state in React Context在 React Context 中与全局状态交互时过度重新渲染
【发布时间】:2021-12-07 00:28:47
【问题描述】:

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

由于上下文,正在发生很多重新渲染,每次我在输入中键入一个字母时,所有组件都会重新渲染,当我切换 RightBar 时,它的状态也驻留在上下文中,因为我需要从Navbar 中的一个按钮。

我尝试在每个组件上使用memo,但每次我与来自任何组件的上下文中的状态交互时,仍然会重新呈现所有组件。

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

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

我尝试将我的代码分成一些自定义钩子,如 useChatSericeuseUsersService 以简化代码并使实际组件干净,我还将感谢任何有关如何更好地构建这些钩子以及放置位置的见解CRUD 功能,同时避免过度重新渲染。

我发现了一些“解决方案”,表明使用多个上下文应该会有所帮助,但我无法弄清楚在我的具体情况下如何做到这一点,被这个问题困扰了一周。

编辑:

  • 这里的主要问题是完全重新渲染输入中输入的每个字母。
  • 第二个是RightBar 切换按钮,它也会导致完全重新渲染。

【问题讨论】:

    标签: reactjs react-hooks react-context


    【解决方案1】:

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

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

    我建议将 rightBarValue 和 state setter 加入到自己的上下文中。

    导航栏上下文

    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>
      );
    };
    

    NavBar - 使用 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" 仅在聊天文本区域中键入时记录。

    【讨论】:

    • 非常感谢!这很有帮助!你能解释一下useMemo那里的用法吗?
    • @dwix 当您value={{ ... values ... }} 时,您仍在创建一个新的对象引用每个渲染周期。可以记住此上下文值以提供稳定的参考并进一步帮助减少不必要的渲染。
    • 我明白了,谢谢!
    • 在我看来,每次输入字符时聊天组件仍会呈现
    • @CarmineTambascia 是的,Chat 正在接收聊天上下文并看到该上下文中的更改,从而重新渲染。
    【解决方案2】:

    我建议使用jotai 或其他状态管理库。
    上下文不适合高频变化。
    而且,RightBar 的状态看起来可以与其他钩子/上下文分开。

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

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

    【讨论】:

      【解决方案3】:

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

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2021-08-21
        • 2020-11-21
        • 2020-10-24
        • 1970-01-01
        • 2021-06-11
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多