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

时间:2018-06-12 12:49:15

标签: javascript reactjs react-context

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

检查 this small example on Codesandbox

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

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

我从此处创建了一个新的React Context,其中包含来自州的numbertext,并将值传递给两个使用者NumberText

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

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

所以,我的问题 -

  1. 对上下文的更新是否不会通过因使用的rerenders传播?因为当上下文改变时我看不到我的日志/颜色变化。

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

2 个答案:

答案 0 :(得分:13)

以下是根据useContext钩子对您的问题的更新:

const value = useContext(MyContext)

当组件上方最近的<MyContext.Provider>更新时,此挂钩将触发重新渲染,并将最新的上下文value传递给该MyContext提供者。即使祖先使用React.memoshouldComponentUpdate,从使用useContext 在组件本身开始,仍会发生 renderer

组件调用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>

答案 1 :(得分:12)

  

对上下文的更新是否不会通过因使用的rerenders传播?   因为在上下文发生变化时我看不到我的日志/颜色变化。

对上下文值的更新不会触发提供者的所有子项的重新呈现,而只触发从Consumer中呈现的组件,因此在您的情况下,虽然数字组件包含Consumer,但数组件不是&n #39;重新渲染,而不仅仅是消费者中的渲染功能,因此值更改了上下文更新。这样它的性能非常高,因为它不会触发所有孩子的重新渲染。

  

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

该提供商的所有消费者都将经历更新周期,但他们是否重新呈现是由反应虚拟DOM比较决定的。您可以在控制台中看到此 sandbox

的演示

修改

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

之内

高效使用

App.js

  render() {
    return (
      <AppContext.Provider
        value={{ ...this.state, updateNumber: this.updateNumber }}
      >
        {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>
    );
  }
}