Реакция: Когда поставщик контекста обновляет своих потребителей? - PullRequest
1 голос
/ 28 марта 2019

Насколько я понимаю, контекст React Провайдер обновляет своих Потребителей всякий раз, когда значение контекста изменяется.

Из документов :

Все потребители, которые являются потомками провайдера, будут перерисовывать всякий раз, когда меняется 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 увидеть, что действительно не было любым изменением на значение контекста
  2. Дети выполнят повторную визуализацию, потому что это сделал их родитель, но этот процесс остановился в середине процесса Toolbox, равным PureComponent
  3. Теперь вся идея контекста Provider заключается в том, что он должен только обновить свое Consumers, если значение контекста изменилось
  4. Проверка изменений выполняется с помощью Object.is, как указано в Документах (см. Выше)
  5. Независимо от этого факта Consumer (ThemedButton) все еще обновляется, когда OtherState меняет
  6. Этого не должно произойти, потому что значение контекста действительно не изменилось , а дочерний повторный рендеринг остановился в середине с PureComponent
  7. Только при изменении значения контекста должно обновляться Consumers, даже если повторный рендеринг остановлен в промежуточных компонентах с PureComponent

PS: Вы можете увидеть, что значение контекста не изменяется при нажатии Change OtherState, просмотрев журнал консоли Object.is.

Вопрос

Почему ThemedButton перерисовывается, если значение контекста не изменилось?

1 Ответ

1 голос
/ 28 марта 2019

useState возвращает новый массив при каждом вызове.Таким образом, вы передаете новый массив в контекст каждого рендера. используйтеMemo для решения проблемы.

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