反应:上下文提供者何时更新其使用者?

时间:2019-03-28 16:54:22

标签: javascript reactjs react-hooks

据我了解,每当 context值更改时,React的上下文 Provider 都会更新其 Consumers

From the Docs

  

作为提供者后代的所有消费者将重新呈现   供应商的value道具发生变化时。从传播   子孙消费者的提供者不受   shouldComponentUpdate方法,因此即使   祖先组件无法进行更新。

     

更改是通过使用   与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看到确实确实存在没有 任何更改为 context值
  2. 孩子将因为父母的照会而重新渲染,但由于ToolboxPureComponent
  3. 而使该过程中途停止
  4. 现在,上下文Provider的整体思想是,如果 context值更改,它应该更新其Consumers
  5. 根据文档中的规定,使用Object.is完成更改检查(请参见上文)
  6. 尽管如此,Consumer更改时ThemedButtonOtherState)仍会更新
  7. 这不应该发生,因为 context值实际上没有不变更改,并且子项重新渲染在中间用PureComponent停止了< / li>
  8. 仅当 context值发生更改时,Consumers才应更新,即使在使用PureComponent的中间组件中停止重新渲染

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

问题

上下文值没有变化时,为什么ThemedButton重新呈现?

1 个答案:

答案 0 :(得分:1)

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"/>