React Context:什么时候重新渲染儿童?

Mag*_*nus 8 javascript reactjs

另一StackOverflow上后,谢霆锋让我了解到,一个Context.Provider重新呈现其子Context.Consumer组件时的上下文值Provider规定变化。

官方文件进一步证实了这一点:

每当 Provider 的 value 属性发生变化时,所有作为 Provider 后代的消费者都会重新渲染。

Nicholas 还帮助我理解了 a Providerwill 知道上下文值是否发生变化的唯一方法,是它的封闭组件是否重新渲染。

总之:

  1. ProvidersConsumers每当上下文值更改时更新它
  2. 这只会在Provider重新渲染周围的封闭函数时发生
  3. Provider无论如何,这会导致及其所有后代重新渲染

因此,上面(1)中的特征似乎是多余的。如果ProviderConsumers在其封闭组件重新渲染时才更新,并且仅在父级重新渲染时发现上下文值更新,则不需要具有允许在上下文值更改时Provider更新的功能。Consumers

我在这里缺少什么?


编辑

尼古拉斯还在评论中说:

由于与通过上下文提供的价值无关的事情,应用程序可以(可以想象)重新渲染。如果发生这种情况,您不希望消费者重新渲染。为此,您需要之前的值和之后的值才能通过 === 检查。如果您提供一个对象,这意味着您不能在 App 的 render 方法中创建一个全新的对象,否则最终会不必要地重新渲染消费者。

但是,我的印象是,当父级重新渲染时,其所有子级也将重新渲染。因此,===上面提到的检查无济于事,即孩子们无论如何都会重新渲染。

Nic*_*wer 8

  1. 这会导致 Provider 及其所有后代重新渲染

虽然这是默认行为,但在实践中,通常会更改此行为以提高性能。纯组件、实现 shouldComponentUpdate 的组件或使用 React.memo 的组件将导致重新渲染在遍历整个树之前停止。

例如:假设有一个具有某种状态的顶级组件,它呈现一个具有 的中级组件shouldComponentUpdate() { return false; },它呈现一个底层组件。

function TopLevelComponent() {
  const [topLevelState, setTopLevelState] = useState(0);
  return (
    <>
      <h1>Top Level Component</h1>
      <button onClick={setTopLevelState( v => v + 1)}>Update Top Level State</button>
      <MidLevelComponent />
    </>
  );
}
class MidLevelComponent extends React.Component {
  shouldComponentUpdate() {
    return false; // <= This guy will prevent re-rendering of this component and everything nested under it
  }
  render() {
    return (
      <>
        <h2>Mid Level Component</h2>
        <BottomLevelComponent />
      </>
    );
  }
}
function BottomLevelComponent() {
  return "Bottom Level";
}
Run Code Online (Sandbox Code Playgroud)

在初始安装时,所有这 3 个都会渲染。但是,如果顶层组件更新其状态,则只有顶层组件才会重新渲染。中层组件将由于其 shouldComponentUpdate 而被跳过,然后底层组件甚至不会被考虑。(请参阅下面的实时代码片段 - 更好地在全页模式下运行)

function TopLevelComponent() {
  const [topLevelState, setTopLevelState] = useState(0);
  return (
    <>
      <h1>Top Level Component</h1>
      <button onClick={setTopLevelState( v => v + 1)}>Update Top Level State</button>
      <MidLevelComponent />
    </>
  );
}
class MidLevelComponent extends React.Component {
  shouldComponentUpdate() {
    return false; // <= This guy will prevent re-rendering of this component and everything nested under it
  }
  render() {
    return (
      <>
        <h2>Mid Level Component</h2>
        <BottomLevelComponent />
      </>
    );
  }
}
function BottomLevelComponent() {
  return "Bottom Level";
}
Run Code Online (Sandbox Code Playgroud)
console.log("--- Initial Render");

function BottomLevelComponent() {
  console.log("BottomLevelComponent() => renders");
  return "Bottom Level";
}

class MidLevelComponent extends React.Component {
  shouldComponentUpdate() {
    return false;
  }
  render() {
    console.log("MidLevelComponent() => renders");
    return (
      <div>
        <h2>Mid Level Component</h2>
        <BottomLevelComponent />
      </div>
    );
  }
}

function TopLevelComponent() {
  console.log("TopLevelComponent() => renders");
  const [topLevelState, setTopLevelState] = React.useState(0);
  const handleTopLevelUpdate = () => {
    console.log("--- Updating Top Level State");
    setTopLevelState((v) => v + 1);
  };
  return (
    <div>
      <h1>Top Level Component</h1>
      <button onClick={handleTopLevelUpdate}>Update Top Level State</button>
      <MidLevelComponent />
    </div>
  );
}

ReactDOM.render(<TopLevelComponent />, document.getElementById("root"));
Run Code Online (Sandbox Code Playgroud)

现在,我们将上下文提供者添加到顶级组件,并将上下文使用者添加到底层组件。在初始安装时,它们将再次全部渲染。如果顶层组件更新其状态,它将重新渲染。由于其 shouldComponentUpdate,中级组件仍将跳过其渲染。但只要上下文值发生变化,底层组件就会重新渲染,即使其父级组件已退出。这就是该简介中提到的功能。

const TopLevelContext = React.createContext();

export default function TopLevelComponent() {
  const [topLevelState, setTopLevelState] = useState(0);
  return (
    <TopLevelContext.Provider value={{ topLevelState }}>
      <h1 onClick={setTopLevelState((v) => v + 1)}>Top Level Component</h1>
      <MidLevelComponent />
    </TopLevelContext.Provider>
  );
}
class MidLevelComponent extends React.Component {
  shouldComponentUpdate() {
    return false; // <= Will prevent rendering of this Component and everything nested under it, but...
  }
  render() {
    return (
      <>
        <h2>Mid Level Component</h2>
        <BottomLevelComponent />
      </>
    );
  }
}
function BottomLevelComponent() {
  React.useContext(TopLevelContext); // <= ...this will override the shouldComponentUpdate of the parent and trigger a re-render when the Context provider value changes
  return "Bottom Level";
}
Run Code Online (Sandbox Code Playgroud)

<script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Run Code Online (Sandbox Code Playgroud)
const TopLevelContext = React.createContext();

export default function TopLevelComponent() {
  const [topLevelState, setTopLevelState] = useState(0);
  return (
    <TopLevelContext.Provider value={{ topLevelState }}>
      <h1 onClick={setTopLevelState((v) => v + 1)}>Top Level Component</h1>
      <MidLevelComponent />
    </TopLevelContext.Provider>
  );
}
class MidLevelComponent extends React.Component {
  shouldComponentUpdate() {
    return false; // <= Will prevent rendering of this Component and everything nested under it, but...
  }
  render() {
    return (
      <>
        <h2>Mid Level Component</h2>
        <BottomLevelComponent />
      </>
    );
  }
}
function BottomLevelComponent() {
  React.useContext(TopLevelContext); // <= ...this will override the shouldComponentUpdate of the parent and trigger a re-render when the Context provider value changes
  return "Bottom Level";
}
Run Code Online (Sandbox Code Playgroud)