• createContext 你用对了吗?


    更好的体验可前往掘金阅读:createContext 你用对了吗?

    前言

    createContext是 react 提供的用于全局状态管理的一个 api,我们可以通过Provider组件注入状态,用Consumer组件或者useContextapi 获取状态(推荐使用useContext方式,更加简洁)。

    createContext让组件间的通信更为方便,但如果使用不当却会带来很大的性能问题。下面我们会讨论引起性能问题的原因以及如何优化。

    性能问题的根源

    先来看一个例子:createContext性能问题原因,注意例子中的2个问题点。

    import { useState, useContext, createContext } from "react";
    import { useWhyDidYouUpdate } from "ahooks";
    
    const ThemeCtx = createContext({});
    
    export default function App() {
      const [theme, setTheme] = useState("dark");
      /**
       * 性能问题原因:
       * ThemeCtx.Provider 父组件渲染导致所有子组件跟着渲染
       */
    
      return (
        <div className="App">
          <ThemeCtx.Provider value={{ theme, setTheme }}>
            <ChangeButton />
            <Theme />
            <Other />
          </ThemeCtx.Provider>
        </div>
      );
    }
    
    function Theme() {
      const ctx = useContext(ThemeCtx);
      const { theme } = ctx;
      useWhyDidYouUpdate("Theme", ctx);
      return <div>theme: {theme}</div>;
    }
    
    function ChangeButton() {
      const ctx = useContext(ThemeCtx);
      const { setTheme } = ctx;
      useWhyDidYouUpdate("Change", ctx);
      // 问题2:value 状态中没有改变的值导致组件渲染
      console.log("setTheme 没有改变,其实我也不应该渲染的!!!");
      return (
        <div>
          <button
            onClick={() => setTheme((v) => (v === "light" ? "dark" : "light"))}
          >
            改变theme
          </button>
        </div>
      );
    }
    
    function Other() {
      // 问题1:和 value 状态无关的子组件渲染
      console.log("Other render。其实我不应该重新渲染的!!!");
      return <div>other组件,讲道理,我不应该渲染的!</div>;
    }
    

    问题1(整体重复渲染):Provider组件包裹的子组件全部渲染

    从这个例子可以看出来,用ThemeCtx.Provider直接包裹子组件,每次ThemeCtx.Provider组件渲染会导致所有子组件跟着重新渲染,原因是使用React.createElement(type, props: {}, ...)创建的组件,每次props: {}都会是一个新的对象。

    问题2(局部重复渲染):使用useContext导致组件渲染

    createContext是根据发布订阅模式来实现的,Providervalue值每次发生变化都会通知所有使用它的组件(使用useContext的组件)重新渲染。

    解决方案

    上面我们分析了问题的根源,下面就开始解决问题。 同样先看一下优化后的例子:createContext性能优化

    import { useState, useContext, createContext, useMemo } from "react";
    import { useWhyDidYouUpdate } from "ahooks";
    import "./styles.css";
    
    const ThemeCtx = createContext({});
    
    export default function App() {
      return (
        <div className="App">
          <ThemeProvide>
            <ChangeButton />
            <Theme />
            <Other />
          </ThemeProvide>
        </div>
      );
    }
    
    function ThemeProvide({ children }) {
      const [theme, setTheme] = useState("dark");
    
      return (
        <ThemeCtx.Provider value={{ theme, setTheme }}>
          {children}
        </ThemeCtx.Provider>
      );
    }
    
    function Theme() {
      const ctx = useContext(ThemeCtx);
      const { theme } = ctx;
      useWhyDidYouUpdate("Theme", ctx);
      return <div>{theme}</div>;
      // return <ThemeCtx.Consumer>{({ theme }) => <div>{theme}</div>}</ThemeCtx.Consumer>;
    }
    
    function ChangeButton() {
      const ctx = useContext(ThemeCtx);
      const { setTheme } = ctx;
      useWhyDidYouUpdate("Change", ctx);
    
      /**
       * 解决方案:使用 useMemo
       *
       */
      const dom = useMemo(() => {
        console.log("re-render Change");
        return (
          <div>
            <button
              onClick={() => setTheme((v) => (v === "light" ? "dark" : "light"))}
            >
              改变theme
            </button>
          </div>
        );
      }, [setTheme]);
    
      return dom;
    }
    
    function Other() {
      console.log("Other render,其实我不应该重新渲染的!!!");
      return <div>other,讲道理,我不应该渲染的!</div>;
    }
    

    解决问题1

    ThemeContext抽离出来,子组件通过propschildren属性传递进来。即使ThemeContext.Provider重新渲染,children也不会改变。这样就不会因为value值改变导致所有子组件跟着重新渲染了。

    解决问题2

    通过上面的方式可以一刀切的解决整体重复渲染的问题,但局部渲染的问题就比较繁琐了,需要我们用useMemo一个个的修改子组件,或者使用React.memo把子组件更加细化。

    参考

    useContext深入学习
    奇怪的useMemo知识增加了
    react usecontext_React性能优化篇

  • 相关阅读:
    解决小问题VC2008通过ADO连接ACCESS (转)
    给zblog站点备份和搬家
    vc中自定义编译时的输出消息 .
    更改MFC 对话框控件的tab顺序
    dotnetbar 添加到工具栏方法
    关于完成端口IOCP异步接收连接函数AcceptEx注意事项 (转)
    中英文url解码vc++源程序
    c#关于网页内容抓取,简单爬虫的实现。(包括动态,静态的)
    图解使用Broadcom Advanced Control Suite 2设置Broadcom单网卡跨多VLAN(转)
    windbg调试环境变量记录
  • 原文地址:https://www.cnblogs.com/chenwenhao/p/15484825.html
Copyright © 2020-2023  润新知