React v16 源码分析 ⑬ 性能优化相关

对于以下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function Child() {
console.log("child");
return <div>2</div>;
}
function App() {
const [state, setState] = useState(1);
console.log("parent");
return (
<div>
<div onClick={() => setState(2)}>click</div>
<Child></Child>
</div>
);
}

首次挂载打印 parent => child;
第一次点击 click 打印 parent=>child
第二次点击 click 打印 parent
第三次点击 click,不打印任何内容

第二次点击的时候没有打印 child 实际上是命中了 bailout 策略,命中该策略的子组件会跳过 reconcile 过程,因此不会进入 render 阶段。

第三次点击没有任何打印,说明父组件和子组件都没有进入 render 阶段,实际上是命中了 eagerState 策略,这是一种发生于触发状态更新时的优化策略,如果命中了该策略不会进入 schedule 阶段,更不会进入 render 阶段。

eagerState

如果某个状态更新前后没有变化就可以跳过更新流程。

state 是基于 update 计算出来的,计算过程发生在 render 的 beginWork, 而 eagerState 则是将计算过程提到了 schedule 之前执行。

前提条件是该 fiberNode 不存在等待执行的更新,如果不存在更新那么就可以作为第一个更新。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
if (
fiber.lanes === NoLanes &&
(alternate === null || alternate.lanes === NoLanes)
) {
// 如果新状态与当前状态相同,我们可以完全摆脱副作用
const lastRenderedReducer = queue.lastRenderedReducer;
if (lastRenderedReducer !== null) {
let prevDispatcher;
try {
const currentState = queue.lastRenderedState; // 就是 memoizedState
const eagerState = lastRenderedReducer(currentState, action); // 基于 action 提前计算 state
// 如果在我们进入渲染阶段时 reducer 没有改变,那我们可以使用 eager 状态而无需再次调用 reducer。
update.hasEagerState = lastRenderedReducer; // 标记该 update 存在 eagerState
update.eagerState = eagerState; // 存储 eagerState 的值
if (is(eagerState, currentState)) {
return;
}
} catch (error) {
// ...
} finally {
// ...
}
}
}

上面的代码中通过 lastRenderedReducer 提前计算 State,如果前后状态没有变化就会命中 eagerState 策略,如果没有命中但是当前更新是 fiberNode 的第一个更新,也可以作为后续更新的 baseState。

为什么第二次点击的时候,父组件还是要渲染一次,因为进入 eagerState 的条件是

1
fiber.lanes === NoLanes && (alternate === null || alternate.lanes === NoLanes);

current.lanes 和 workInProcess.lanes 都需要 NoLanes, 第一次点击时 beginWork 执行结束后 workInProcess.lanes 会被设置为 NoLanes 但此时还没有交换 fiberTree 需要等到 commit 之后执行,因此第一次点击之后,只有 current 被设置为了 NoLanes。
虽然没有命令 eagerState 但是会命中 bailout 这时会被设置为 NoLanes。

1
2
3
4
5
function bailoutHooks(current: Fiber, workInProgress: Fiber, lanes: Lanes) {
workInProgress.updateQueue = current.updateQueue;
// ...
current.lanes = removeLanes(current.lanes, lanes);
}
打赏
  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2015-2025 SunZhiqi

此时无声胜有声!

支付宝
微信