javascript - react : When Does Context Provider Update its Consumers?

标签 javascript reactjs react-hooks

据我了解,只要 context 值 发生变化,React 的 Context Provider 就会更新其 Consumers

From the Docs :

All consumers that are descendants of a Provider will re-render whenever the Provider’s value prop changes. The propagation from Provider to its descendant consumers is not subject to the shouldComponentUpdate method, so the consumer is updated even when an ancestor component bails out of the update.

Changes are determined by comparing the new and old values using the same algorithm as Object.is.

然而,下面的代码似乎表明了相反的意思:

var themes = {
  light: {
    name: "Light",
    foreground: "#000000",
    background: "#eeeeee"
  },
  dark: {
    name: "Dark",
    foreground: "#ffffff",
    background: "#222222"
  }
};

const ThemeContext = React.createContext({
  theme: themes.light,
  updateTheme: () => {}
});

let prevTheme = undefined;

function App() {

  console.log("RE-RENDERING App...");
  const stateArray = React.useState(themes.light);

  const [theme, setTheme] = stateArray;

  const [otherState, setOtherState] = React.useState(true);

  function handleSetOtherState() {
    console.log("SETTING OTHER STATE.....");
    setOtherState(prevState => !prevState);
  }

  console.log("theme:", theme);
  console.log("prevTheme:", prevTheme);
  console.log(`Object.is(prevTheme, theme): ${Object.is(prevTheme, theme)}`);
  prevTheme = theme;
  return (
    <ThemeContext.Provider value={stateArray}>
      <Toolbar />
      <button onClick={handleSetOtherState}>Change OtherState</button>
    </ThemeContext.Provider>
  );
}

class Toolbar extends React.PureComponent {
  render() {
    console.log("RE-RENDERING Toolbar (DOES NOT HAPPEN WHEN CHANGING OTHERSTATE)...");
    return (
      <div>
        <ThemedButton />
      </div>
    );
  }
}

function ThemedButton() {

  console.log("RE-RENDERING ThemedButton (SHOULD NOT HAPPEN WHEN CHANGING OTHERSTATE)...");
  const themeContext = React.useContext(ThemeContext);
  const [theme, setTheme] = themeContext;
  console.log("themeContext:", themeContext);
  console.log("theme.name:", theme.name);
  console.log("setTheme:", setTheme);

  function handleToggleTheme() {
    console.log("SETTING THEME STATE.....");
    setTheme(
      prevState =>
        themes.dark
    );
  }

  return <button onClick={handleToggleTheme}>Click me: {theme.name}</button>;
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
<script crossorigin src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>
<div id="root"/>

如图所示,点击 Change OtherState 时:

  1. 包含上下文的父组件 Provider将重新呈现,从而允许 Provider以查看上下文值
  2. 确实没有任何更改
  3. children 会重新渲染,因为他们的 parent 做了,但是这个过程在过程中途被 Toolbox 停止了。作为PureComponent
  4. 现在,上下文的整体思路Provider , 它应该更新它的 Consumers如果上下文值改变了
  5. 更改检查是用 Object.is 完成的,如文档中所述(见上文)
  6. 不管这个事实,Consumer ( ThemedButton ) 仍然在 OtherState 时更新变化
  7. 这不应该发生,因为上下文值实际上没有改变,并且子重新渲染在中间停止了 PureComponent
  8. 只有当上下文值 发生变化时,Consumers 才应该更新,即使在中间组件中停止重新渲染 PureComponent

PS:您可以看到当Change OtherState上下文值 没有改变。单击,通过查看 Object.is控制台日志。

问题

为什么 ThemedButton上下文值没有改变时重新渲染?

最佳答案

useState 每次调用都会返回一个新数组。因此,您将一个新数组传递给每个渲染的上下文。 useMemo解决问题。

var themes = {
  light: {
    name: "Light",
    foreground: "#000000",
    background: "#eeeeee"
  },
  dark: {
    name: "Dark",
    foreground: "#ffffff",
    background: "#222222"
  }
};

const ThemeContext = React.createContext({
  theme: themes.light,
  updateTheme: () => {}
});

let prevTheme = undefined;
let prevStateArray = undefined;

function App() {

  console.log("RE-RENDERING App...");
  const stateArray = React.useState(themes.light);
  console.log('stateArray', prevStateArray, stateArray, Object.is(prevStateArray, stateArray));
  prevStateArray = stateArray;

  const [theme, setTheme] = stateArray;
  const memoState = React.useMemo(() => [theme, setTheme], [theme, setTheme]);

  const [otherState, setOtherState] = React.useState(true);

  function handleSetOtherState() {
    console.log("SETTING OTHER STATE.....");
    setOtherState(prevState => !prevState);
  }

  console.log("theme:", theme);
  console.log("prevTheme:", prevTheme);
  console.log(`Object.is(prevTheme, theme): ${Object.is(prevTheme, theme)}`);
  prevTheme = theme;
  return (
    <ThemeContext.Provider value={memoState}>
      <Toolbar />
      <button onClick={handleSetOtherState}>Change OtherState</button>
    </ThemeContext.Provider>
  );
}

class Toolbar extends React.PureComponent {
  render() {
    console.log("RE-RENDERING Toolbar (DOES NOT HAPPEN WHEN CHANGING OTHERSTATE)...");
    return (
      <div>
        <ThemedButton />
      </div>
    );
  }
}

function ThemedButton() {

  console.log("RE-RENDERING ThemedButton (SHOULD NOT HAPPEN WHEN CHANGING OTHERSTATE)...");
  const themeContext = React.useContext(ThemeContext);
  const [theme, setTheme] = themeContext;
  console.log("themeContext:", themeContext);
  console.log("theme.name:", theme.name);
  console.log("setTheme:", setTheme);

  function handleToggleTheme() {
    console.log("SETTING THEME STATE.....");
    setTheme(
      prevState =>
        themes.dark
    );
  }

  return <button onClick={handleToggleTheme}>Click me: {theme.name}</button>;
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
<script crossorigin src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>
<div id="root"/>

关于javascript - react : When Does Context Provider Update its Consumers?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55403051/

相关文章:

javascript - 无法在箭头函数中调用 useState Hook

javascript - 从 "class"更改为 "React Hooks"

javascript - 动态文本抓取

javascript - 在提交表单之前使用 ajax 验证字段

javascript - AngularJS:如何将 HTML 页面包含到对话框中?

asp.net - 未处理的拒绝(错误): Could not load settings for 'WebPortal' - ASP. NET Core React

reactjs - 使用 redux-form 在多个不同的挂载点绑定(bind)表单

javascript - 在 React.JS 中查找函数

react-native - React Native 异步等待调度存储

javascript - 从无动画到 block 动画,javascript