💎 第三座大山:Lanu模型
React 的 fiber 架构最重要的功能,就是可中断式递归组件根据状态优先级更新页面。那么 React 是怎么确定优先级的呢?
答案是Lane
模型(又称为 Fiber Lane),
❝expressTime被抛弃了
❞
🚀 Lanu模型思想
Lane 模型是 React 中的一种状态更新机制,它的核心思想是将 UI 中的状态变化抽象成一系列的 “lane”(变化),每个 “lane” 只描述了一个状态的变化,而不是一次完整的状态更新。这样可以使得状态变化更加清晰,易于处理和维护。
举个例子,假设我们有一个计数器组件,它包含一个按钮和一个文本框,点击按钮会将文本框中的值加一。我们可以使用 Lane 模型来拆分状态变化,将状态变化分为两个小的状态变化:
const [count, setCount] = useState(0); // 更新方式1 function handleClick1() { setCount(count + 1); } // 更新方式2 function handleClick2() { setCount((prevCount) => prevCount + 1); }
更新方式1中,setCount
更新的是一个计算表达式的结果,所以无法识别它的具体含义。
更新方式2则将状态更新拆分成了两个小的状态变化:
- 获取当前的
count
值,即prevCount
。 - 将
prevCount
的值加一,得到新的count
值
这种更新方式更加精准,因为React可以正确地识别状态变化,并将其拆分成多个小的状态变化。
我们来看下如下的例子:
function App() { const [count, setCount] = useState(0) const onClick = () => setCount(count + 1) const onAsyncClick = () => { setTimeout(() => { setCount(count + 1) }, 2000) } return ( <div> <div>{count}</div> <button onClick={onClick}>add</button> <button onClick={onAsyncClick}>async add</button> </div> ) } export default App
再点击async add
按钮的2秒内,我们点击add
按钮两次,count
先由0变为2,2秒后,又变为1了。
如果我们采用下面的方式更新状态
const onAsyncClick = () => { setTimeout(() => { setCount(count => count + 1) }, 2000) }
那么2秒后,则会按预期一样,更新为3。
🚀 Lane 模型原理
在 React 中,Lane 模型是一种用于调度更新任务的机制,其目的是提高应用的性能和响应速度。React中涉及的 Lane 主要有以下几种:
- SyncLane:同步更新 Lane,用于处理需要立即得到执行的更新任务,例如由 ReactDom.render() 或 ReactDOMServer.renderToString() 触发的更新任务。
- InputContinuousLane:连续
- DefaultLane:默认 Lane,用于处理普通的更新任务,例如由 useEffect() 或 useLayoutEffect() 触发的更新任务。
- IdleLane:空闲 Lane,用于处理空闲时需要执行的更新任务,例如预加载图片等。
Lane的具体值为32位数字,
const SyncLane: Lane = /* */ 0b0000000000000000000000000000010; const IdleLane: Lane = /* */ 0b0100000000000000000000000000000; const InputContinuousLane: Lane = /* */ 0b0000000000000000000000000001000; const DefaultLane: Lane = /* */ 0b0000000000000000000000000100000;
这些 Lane 的优先级顺序依次降低,SyncLane 的优先级最高,IdleLane 的优先级最低。
而在 React 源码中,相关的 EventPriority 定义在 ReactEventPriorities.js 文件中,其定义如下:
const DiscreteEventPriority = SyncLane; const ContinuousEventPriority = InputContinuousLane; const DefaultEventPriority = DefaultLane; const IdleEventPriority = IdleLane;
以下里四个事件优先级:
- 离散事件(DiscreteEvent):指需要立即执行的事件,例如输入框的 onChange 事件。这些事件需要立即得到响应,以保证应用的交互性能。
- 用户交互事件(UserBlockingEvent):指与用户交互相关的事件,例如点击、滚动等。这些事件也需要尽快得到响应,以提供流畅的用户体验。
- 普通事件(NormalEvent):指一般的更新事件,例如数据更新、网络请求等。这些事件的优先级较低,可以等待一段时间再执行。
- 空闲事件(IdleEvent):指可以在浏览器空闲时执行的事件,例如预加载图片等。这些事件的优先级最低,只有在没有其他任务需要执行时才会执行。
首先,React 每次更新状态会将同类型的 Lane 合并形成 Lanes,然后从同类型的 Lanes 中找出优先级最高的事件。
这里有两个问题:
- Lane 是如何合并成 Lanes 的?
- 怎么从 Lanes 中找出优先级最高的?
首先,React 通过位运算 lane & lane 判断两个 lane 是否是同一类型,如果是,再使用 lane | lane 将 lane 合并成 lanes。
function mergeLanes(a, b) { return a | b; } function intersectLanes(a, b) { return a & b; } queueLanes = intersectLanes(queueLanes, root.pendingLanes); const newQueueLanes = mergeLanes(queueLanes, lane); queue.lanes = newQueueLanes;
需要更新状态时,使用 lanes & -lanes 从相同的 lanes 中找出优先级最高的 lane
function getHighestPriorityLane(lanes) { return lanes & -lanes; }
然后将这个 lane 转为对应的 EventPriority。
function lanesToEventPriority(lanes) { const lane = getHighestPriorityLane(lanes); if (!isHigherEventPriority(DiscreteEventPriority, lane)) { return DiscreteEventPriority; } if (!isHigherEventPriority(ContinuousEventPriority, lane)) { return ContinuousEventPriority; } if (includesNonIdleWork(lane)) { return DefaultEventPriority; } return IdleEventPriority; }
当事件需要处理时,React 总会将优先级最高的事件交给 Scheduler (调度包)转换为更新任务,并将其加入任务队列中。任务队列中的任务按照事件优先级从高到低排序,以确保高优先级任务优先执行。
再 Scheduler 中又会将 EventPriority 优先级转换为任务优先级。然后根据任务优先级进行排序。
let schedulerPriorityLevel; switch (lanesToEventPriority(nextLanes)) { case DiscreteEventPriority: schedulerPriorityLevel = ImmediatePriority; break; case ContinuousEventPriority: schedulerPriorityLevel = UserBlockingPriority; break; case DefaultEventPriority: schedulerPriorityLevel = NormalPriority; break; case IdleEventPriority: schedulerPriorityLevel = IdlePriority; break; default: schedulerPriorityLevel = NormalPriority; break;
任务优先级和 EventPriority 优先级对应该关系也如上面代码 switch 的对应关系。
❝详细的任务调度流程十分复杂,我以后会专门说
❞
🚀 位运算原理
在 JavaScript 中,位运算是一种操作二进制数字的运算。它们利用数字的二进制表示来执行按位操作。以3和5为例,3 的二进制为 0011,5 的二进制为 0101,我们看如下的运算:
- 与运算(&):只有在两个数的对应位都为1时,结果的对应位才为1,否则为0。3 & 5 的结果为 0001。
- 或运算(|):只有在两个数的对应位都为0时,结果的对应位才为0,否则为1。3 | 5 的结果为 0111。
- 非运算(~):将一个数的二进制位按位取反,即0变为1,1变为0。~3 的结果为 1100
🎉 总结
Lane模型贯穿 React 更新的整个流程,是底层更新最重要的部分,本文讲述了Lane模型的原理以及再源码中的一些体现,后面的文章中我们会继续深入一些比较复杂的知识点,打通源码阅读的障碍。
今天的分享就到了,如果文章中有纰漏的地方可以告诉我,我会及时地更正。
所以对你有帮助话请给我点下赞,这对我很重要!