跳到主要内容

React Rerender的时机

来源:Why React Re-Renders

说实话,当我们日常工作中不理理解 React 的rerender 机制究竟是怎么运作的话,这会导致我们在编码中遇到许多不确定性。

React的核心循环(The core React loop)

众所周知,所有 React 中的 Rerender 起源于状态的变化,过去可能还有forceUpdate方法可以触发rerender,不过这个方法已经不复存在了。

不过这个说法好像有问题,如果 rerender 仅存在于 props 的变化,那么 context 呢?

那么这里还有一个条件,就是当 react 组件渲染的同时,他会把子组件全部更新。

因此 React 有两个 rerender 的时机:

  • 组件内状态的变化
  • 上层组件的刷新

可以看下面这个例子:

import React from 'react';

function App() {
return (
<>
<Counter />
<footer>
<p>Copyright 2022 Big Count Inc.</p>
</footer>
</>
);
}

function Counter() {
const [count, setCount] = React.useState(0);

return (
<main>
<BigCountNumber count={count} />
<button onClick={() => setCount(count + 1)}>
Increment
</button>
</main>
);
}

function BigCountNumber({ count }) {
return (
<p>
<span className="prefix">Count:</span>
{count}
</p>
);
}

export default App;

在这个例子中,我们有三个组件,App是最顶层的,渲染了CounterCounter渲染了BigCountNumber

在React中,所有状态变量依附在各自的组件事例上。在这个例子中,我们有一个简单的count状态在Counter组件中。

无论何时这个状态改变,Counter都会rerender。而且因为BigCounterNumber是被Counter渲染的,那么BigCounterNumber也会重新渲染。

这是一个互动图表现了这个行为。

好的,现在我们对第一个误解有了很清晰的判断:整个app都会在一个状态变化后重新渲染。

我知道有些开发者相信所有的state变化都会引起整个react应用重新渲染,但是这个不是正确的,rerender仅仅影响状态变化的组件本身以及他的子组件(如果有的话)。app组件,在这个例子不会rerender即使count状态变化。

与其记住这条规则,我们不如停下脚步去搞清楚为什么react会这样做

react的主任务进程通过state以异步的形式去维持应用层UI。它的主要任务就是去搞清楚什么东西是需要rerender的。

让我们再次以Counter组件举例,当应用第一次挂载的时候,react渲染整个组件,组件的dom树结构如下图

<main>
<p>
<span class="prefix">Count:</span>
0
</p>
<button>
Increment
</button>
</main>
<footer>
<p>Copyright 2022 Big Count Inc.</p>
</footer>

当用户点击button的时候,counte状态从0变成1。如何影响ui?这是我们需要从另一次渲染中学习到的事

react 重新运行Counter和BigCountNumber组件的代码,然后生成我们希望的一个新的dom树结构:

<main>
<p>
<span class="prefix">Count:</span>
1
</p>
<button>
Increment
</button>
</main>
<footer>
<p>Copyright 2022 Big Count Inc.</p>
</footer>

每次渲染都有一个dom树快照,显示了UI需要如何展示。

react使用一种找不同的方式去在两个快照中理清到底什么改变了。在这个情况下,看起来就一个textnode从0变成了1,然后编辑这个节点使他从0变成1。这个事情完美的结束了,react队列等待下次改变。

这就是react的核心循环。