javascript 新的 React Context API 是否会触发重新渲染?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/50817672/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-29 09:17:18  来源:igfitidea点击:

Does new React Context API trigger re-renders?

javascriptreactjsreact-context

提问by Sachin

I have been trying to understand the new React Context API and was playing with it. I just wanted to check a simple case - what all re-renders when data to a Provider is updated.

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

Check this small example on Codesandbox

检查Codesandbox 上的这个小例子

So, in my example, I have an Appcomponent - that has state something like this --

所以,在我的例子中,我有一个App组件——它有这样的状态——

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

I create a new React Context from here containing numberand textfrom state and pass the values to two Consumers Numberand Text.

我从这里创建了一个新的 React Context 包含numbertext来自 state 并将值传递给两个 ConsumersNumberText.

So my assumption is if the random number updates, it will change the context and both the components should trigger re-render.

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

But in reality, the value is updating but no rerender is happening.

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

So, my question -

所以,我的问题 -

  1. Are updated to the context not propagated via the ususal rerenders? As I cannot see my logs / color changes when context changes.

  2. Are all the consumers to that Provider updated or not?

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

  2. 该 Provider 的所有消费者是否都更新了?

回答by Shubham Khatri

Are updated to the context not propagated via the ususal rerenders? As I cannot see my logs / color changes when context changes.

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

The updates to context values doesn't trigger re-render for all the children of the provider, rather only components that are rendered from within the Consumer, so in your case although number component contains the Consumer, Number component isn't re-rendered, rather just the render function within the Consumer and hence the value changes on context updates. This way it is quite a lot performant as it doesn't trigger re-renders for all of its children.

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

Are all the consumers to that Provider updated or not ?

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

All consumers to that Provider will go through an update cycle but whether or not they re-render is decided by the react virtual DOM comparison. A demo of this you can see in the console for this sandbox

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

EDIT

编辑

What you need to make sure is that the components are rendered as children of the ContextProvider component and you are passing handlers to it instead of rendering them inline and updating the state of ContextProvider because that will trigger a re-render of all components that are within the ContextProvider

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

Performant usage

性能使用

App.js

应用程序.js

  render() {
    return (
      <AppContext.Provider
        value={{ ...this.state, updateNumber: this.updateNumber }}
      >
        {this.props.children}
      </AppContext.Provider>
    );
  }

index.js

索引.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
);

Less Performant usage

低性能使用

App.js

应用程序.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>
    );
  }
}

回答by ford04

Here is an update for your questions based on the useContextHook:

以下是基于useContextHook 的问题更新:

const value = useContext(MyContext)

When the nearest <MyContext.Provider>above the component updates, this Hook will trigger a rerender with the latest context valuepassed to that MyContextprovider. Even if an ancestor uses React.memoor shouldComponentUpdate, a rerenderwill still happen starting at the component itself using useContext.

A componentcalling useContextwill always re-renderwhen the context value changes. If re-rendering the component is expensive, you can optimize it by using memoization.

const value = useContext(MyContext)

<MyContext.Provider>组件上方最近的更新时,此 Hook 将使用value传递给该MyContext提供程序的最新上下文触发重新渲染。即使祖先的用途React.memo或者shouldComponentUpdate,一个重新呈现也会出现开始在组件本身使用useContext

一个组件调用useContext始终重新渲染的时候上下文值的变化。如果重新渲染组件的成本很高,您可以使用 memoization 对其进行优化。

So given below code example, components Numberand Textwill re-render with each context value change, as both directly contain useContext(AppContext).

因此给出下面的代码示例,组件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>