React Context Provider 所有子项重新渲染

React Context Provider all children re rendering

谁能给我解释一下为什么下一段代码会重新渲染提供程序的所有子组件

import { createContext, useContext, useState } from "react";

const ThemeContext = createContext();

const App = () => {
    const [theme, setTheme] = useState(false);
    console.log("App running");
    return (
        <ThemeContext.Provider value={{ theme, setTheme }} children={<Child1 />} />
    );
};

const Child1 = () => {
    console.log("Child1 running");
    return (
        <div className="child1">
            <Child2 />
        </div>
    );
};

const Child2 = () => {
    console.log("Child2 running");
    return (
        <div className="child2">
            <Child3 />
        </div>
    );
};

const Child3 = () => {
    const { theme, setTheme } = useContext(ThemeContext);
    console.log("Child3 running");
    return (
        <div className="child3">
            <p>{theme ? "dark" : "light"}</p>
            <button onClick={() => setTheme(!theme)}>Change theme</button>
        </div>
    );
};

export default App;

控制台每次点击按钮,所有组件重新渲染

App running
Child1 running
Child2 running
Child3 running
App running
Child1 running
Child2 running
Child3 running

但是如果上下文提供者被包裹在一个组件中,如下所示

import { createContext, useContext, useState } from "react";

const ThemeContext = createContext();

const ThemeProvider = ({ children }) => {
    const [theme, setTheme] = useState(false);
    console.log("ThemeProvider running");
    return (
        <ThemeContext.Provider value={{ theme, setTheme }} children={children} />
    );
};

const App = () => {
    console.log("App running");
    return <ThemeProvider children={<Child1 />} />;
};

const Child1 = () => {
    console.log("Child1 running");
    return (
        <div className="child1">
            <Child2 />
        </div>
    );
};

const Child2 = () => {
    console.log("Child2 running");
    return (
        <div className="child2">
            <Child3 />
        </div>
    );
};

const Child3 = () => {
    const { theme, setTheme } = useContext(ThemeContext);
    console.log("Child3 running");
    return (
        <div className="child3">
            <p>{theme ? "dark" : "light"}</p>
            <button onClick={() => setTheme(!theme)}>Change theme</button>
        </div>
    );
};

export default App;

单击按钮时的控制台

ThemeProvider running
Child3 running
ThemeProvider running
Child3 running
ThemeProvider running
Child3 running

只有使用上下文的组件(和组件上下文提供者)正在重新渲染

React 究竟是如何处理这种情况的

编辑:

反应版本是 17.0.1 顺便说一句

发生这种情况是因为 <Context.Provider> 在其子属性 与前一个子属性不共享引用相等性 时重新呈现。

在第一个示例中,每次 App 重新渲染时,都会创建一个 new Child1 React 元素。

基本上就像你在做这样的事情:

const App = () => {
  const [theme, setTheme] = useState(false);
  console.log("App running");
  return React.createElement(ThemeContext.Provider, {
    value: {
      theme: theme,
      setTheme: setTheme
    },
    children: React.createElement(Child1, null) <= Notice how the children prop is new with every re-render
  });
};

最终重新呈现 Child1Child2Child3


在第二个示例中,React 元素 Child1App 中创建了一次,然后传递给 ThemeProvider,这意味着在 ThemeProvider 中你是实际上引用 same React 元素,而不是在每次重新渲染时都创建一个新元素,因此在这种情况下,只有关联的消费者组件(Child3)会重新渲染。