74

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

在 Codesandbox 上查看这个小例子

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

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

我从这里创建了一个新的 React 上下文,包含numbertextfrom state,并将值传递给两个 ConsumersNumberText.

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

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

所以,我的问题——

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

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

4

2 回答 2

58

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

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

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

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

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

更少的性能使用

应用程序.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>
    );
  }
}
于 2018-06-12T12:56:57.553 回答
35

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

const value = useContext(MyContext)

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

上下文值更改时,组件调用useContext始终重新呈现。如果重新渲染组件的成本很高,您可以使用记忆化对其进行优化。

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

于 2020-03-08T16:50:53.407 回答