【问题标题】:Does new React Context API trigger re-renders?新的 React Context API 会触发重新渲染吗?
【发布时间】:2022-01-18 04:39:12
【问题描述】:

我一直在尝试理解新的 React Context API 并且正在使用它。我只是想检查一个简单的案例 - 更新到 Provider 的数据时所有重新呈现的内容。

检查this small example on Codesandbox

因此,在我的示例中,我有一个 App 组件 - 具有类似这样的状态 -

this.state = {
  number - A random number
  text - A static text
} 

我从这里创建一个新的 React 上下文,其中包含来自 state 的 numbertext,并将值传递给两个消费者 NumberText

所以我的假设是如果随机数更新,它将改变上下文并且两个组件都应该触发重新渲染。

但实际上,值正在更新,但没有发生重新渲染。

所以,我的问题-

  1. 是否已更新为未通过通常的重新渲染传播的上下文?因为当上下文发生变化时,我看不到我的日志/颜色变化。

  2. 该提供者的所有消费者是否都已更新?

【问题讨论】:

    标签: javascript reactjs react-context


    【解决方案1】:

    是否更新到不通过通常的重新渲染传播的上下文?因为当上下文发生变化时,我看不到我的日志/颜色变化。

    上下文值的更新不会触发提供者的所有子级的重新渲染,而只会触发从消费者内部渲染的组件,因此在您的情况下,虽然数字组件包含消费者,但数字组件不是重新渲染,而不仅仅是 Consumer 中的渲染函数,因此值会在上下文更新时发生变化。这种方式的性能非常好,因为它不会触发所有子级的重新渲染。

    该提供者的所有消费者是否都更新了?

    该 Provider 的所有消费者都将经历一个更新周期,但他们是否重新渲染取决于 react 虚拟 DOM 比较。您可以在控制台中看到此 sandbox

    的演示

    编辑

    您需要确保组件被渲染为 ContextProvider 组件的子组件,并且您将处理程序传递给它,而不是内联渲染它们并更新 ContextProvider 的状态,因为这将触发所有组件的重新渲染在ContextProvider

    高效使用

    App.js

      constructor() {
        super();
        this.state = {
          number: Math.random() * 100,
          text: "testing context api"
          updateNumber: this.updateNumber,
        };
      }
      render() {
        return (
          <AppContext.Provider
            value={this.state}
          >
            {this.props.children}
          </AppContext.Provider>
        );
      }
    

    index.js

    class Data extends React.Component {
      render() {
        return (
          <div>
            <h1>Welcome to React</h1>
            <Number />
            <Text />
            <TestComp />
            <AppContext.Consumer>
              {({ updateNumber }) => (
                <button onClick={updateNumber}>Change Number </button>
              )}
            </AppContext.Consumer>
          </div>
        );
      }
    }
    
    const rootElement = document.getElementById("root");
    ReactDOM.render(
      <App>
        <Data />
      </App>,
      rootElement
    );
    

    使用性能较差

    App.js

    class App extends Component {
      constructor() {
        super();
        this.state = {
          number: Math.random() * 100,
          text: "testing context api"
        };
      }
    
      updateNumber = () => {
        const randomNumber = Math.random() * 100;
        this.setState({ number: randomNumber });
      };
    
      render() {
        return (
          <AppContext.Provider value={this.state}>
            <div>
              <h1>Welcome to React</h1>
              <Number />
              <Text />
              <TestComp />
              <button onClick={this.updateNumber}>Change Number </button>
            </div>
          </AppContext.Provider>
        );
      }
    }
    

    【讨论】:

    • 我对此很感兴趣,因为我有类似的问题。对我来说,因为有 2 个 console.logs 并且只有一项更改 - 我认为仍然有 2 个渲染正在进行,我认为其中一个是不必要的。我的想法是只有一个项目应该更新,因此只有一个 console.log - 为什么不是这种情况?您将如何实现这一结果?
    • 为什么内联更新状态会触发重新渲染所有子组件而分离不会呢?
    • @TomSawyer,这是因为当您将它们内联渲染时,它们位于您的 Provider 组件的层次结构中,并且会在提供者的状态更改时重新渲染
    • useContext 呢?
    • @ShubhamKhatri:尽管Provider 的所有子组件不会在value 更改时重新渲染,但我相信消费者组件的所有子组件仍然会重新渲染?
    【解决方案2】:

    以下是基于useContext Hook 的问题更新:

    const value = useContext(MyContext)

    当组件上方最近的 &lt;MyContext.Provider&gt; 更新时,此 Hook 将触发重新渲染,并将最新的上下文 value 传递给该 MyContext 提供程序。即使祖先使用React.memoshouldComponentUpdate重新渲染 仍然会从使用useContext 的组件本身开始。

    上下文值改变时,调用useContext组件总是重新渲染。如果重新渲染组件代价高昂,您可以使用 memoization 对其进行优化。

    所以在下面的代码示例中,组件NumberText 将随着上下文值的变化而重新渲染,因为它们都直接包含useContext(AppContext)

    const AppContext = React.createContext();
    
    const Number = React.memo(props => {
      const renderCount = useRenderCount();
      const contextNo = React.useContext(AppContext);
      return (
        <div style={{ backgroundColor: `${randomColor()}` }}>
          Number: rendered {renderCount.current} times.
        </div>
      );
    });
    
    const Text = React.memo(() => {
      const renderCount = useRenderCount();
      const context = React.useContext(AppContext);
      return (
        <div style={{ backgroundColor: `${randomColor()}` }}>
          Text: rendered {renderCount.current} times. I rerender with context value
          changes!
        </div>
      );
    });
    
    const App = () => {
      const [ctxVal, setCtxVal] = React.useState(0);
      const [prop, setProp] = React.useState(0);
      return (
        <AppContext.Provider value={ctxVal}>
          <Number prop={prop} />
          <Text />
          <button onClick={() => setCtxVal(ctxVal + 1)}>
            Change context value
          </button>
          <button onClick={() => setProp(prop + 1)}>
            Only change prop in Number
          </button>
        </AppContext.Provider>
      );
    };
    
    function useRenderCount() {
      const renderCount = React.useRef(1);
      React.useEffect(() => {
        renderCount.current += 1;
      }); 
      return renderCount;
    }
    
    function randomColor() {
      const letters = "0123456789ABCDEF"; let color = "#";
      for (let i = 0; i < 6; i++) color += letters[Math.floor(Math.random() * 16)];
      return color;
    }
    
    ReactDOM.render(<App />, document.getElementById("root"));
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js" integrity="sha256-32Gmw5rBDXyMjg/73FgpukoTZdMrxuYW7tj8adbN8z4=" crossorigin="anonymous"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js" integrity="sha256-bjQ42ac3EN0GqK40pC9gGi/YixvKyZ24qMP/9HiGW7w=" crossorigin="anonymous"></script>
    <div id="root"></div>

    【讨论】:

    • @ford04 使用对象作为 ctxVal 的示例的扩展在这里:codesandbox。感谢作者!我需要快速展示对象更改检测如何工作的示例,并且能够快速使用您的示例作为基础。
    • 你提到它不关心父级是否有 should 组件或 React,memo 。但就我而言,我记住了使用 useContext 的父级,并且当 contextapi 中的值发生更改时,它不会重新渲染..
    • @BryanLumbantobing 为使用 useContext 的组件触发重新渲染以防发生值更改。如果您的子组件被 React.memo 包裹,并且它的 props 没有改变,那么尽管父上下文发生了变化,这个子组件也不会重新渲染。
    • 如果context const只是声明了,没有在return方法中使用,Text组件怎么会根据context更新它的状态呢?声明组件内部的上下文是否足以进行重新渲染?不应该 context.someVariable 然后在返回方法中使用,以便在状态更新后将其更改传播到此 Text 组件?
    • @FedericoCapaldo 是的,当上下文值发生变化时,组件包含useContext ro rerender 就足够了。
    猜你喜欢
    • 1970-01-01
    • 2018-12-21
    • 2021-01-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-06-02
    相关资源
    最近更新 更多