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

IT技术 javascript reactjs react-context
2021-02-27 11:40:09

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

检查Codesandbox 上的这个小例子

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

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

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

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

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

所以,我的问题 -

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

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

2个回答

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

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

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

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

编辑

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

性能使用

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

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

低性能使用

应用程序.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 - 为什么不是这种情况?你将如何实现这个结果?
2021-04-30 11:40:09
@TomSawyer,这是因为当您内联渲染它们时,它们位于您的 Provider 组件的层次结构内,并且会在提供程序的状态更改时重新渲染
2021-05-05 11:40:09
useContext 呢?
2021-05-08 11:40:09
为什么更新状态内联可以触发重新渲染所有子组件并分离它不会?
2021-05-12 11:40:09

以下是基于useContextHook 的问题更新

const value = useContext(MyContext)

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

你提到它不关心父级是否应该组件或 React,memo 。但在我的情况下,我记住了我使用 useContext 的父级,并且当 contextapi 中的值发生变化时,它不会重新渲染..
2021-04-16 11:40:09
Text组件如何根据contextifcontext const仅声明但未在 return 方法中使用更新其状态是否足以在组件内声明上下文以进行重新渲染?context.someVariable应该在 return 方法中使用它的更改,然后Text在状态更新后将其传播到此组件吗?
2021-04-22 11:40:09
使用对象作为 ctxVal 的@ford04 示例的扩展在这里:codesandbox感谢作者!我需要快速展示一个对象变化检测如何工作的示例,并且能够快速使用您的示例作为基础。
2021-04-25 11:40:09
@FedericoCapaldo 是的,useContext当上下文值更改时,组件包含重新渲染就足够了
2021-04-27 11:40:09
@BryanLumbantobing 为在useContext值更改时使用的组件触发重新渲染如果您的子组件被 包裹React.memo,并且它的 props 没有改变,那么尽管父上下文发生了变化,这个子组件也不会重新渲染。
2021-04-28 11:40:09