🎉干货满满,React设计原理(三):藏在源码里的排位赛,Lane模型🎉

简介: 🎉干货满满,React设计原理(三):藏在源码里的排位赛,Lane模型🎉


💎 第三座大山: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则将状态更新拆分成了两个小的状态变化:

  1. 获取当前的 count 值,即 prevCount
  2. 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模型的原理以及再源码中的一些体现,后面的文章中我们会继续深入一些比较复杂的知识点,打通源码阅读的障碍。

今天的分享就到了,如果文章中有纰漏的地方可以告诉我,我会及时地更正。

所以对你有帮助话请给我点下赞,这对我很重要!

相关文章
|
4月前
|
算法 前端开发 JavaScript
React的diff算法原理
React的diff算法原理
98 0
|
4月前
|
JSON 缓存 前端开发
【React】React原理面试题集锦
本文集合一些React的原理面试题,方便读者以后面试查漏补缺。作者给出自认为可以让面试官满意的简易答案,如果想要了解更深刻,可以点击链接查看对应的详细博文。在此对链接中的博文作者非常感谢🙏。
111 0
|
3月前
|
前端开发 网络协议 测试技术
探索PHP的异步编程模型:从React到Swoole
在Web开发领域,PHP一直以简单易用著称。然而,随着互联网应用对性能和并发处理能力的不断追求,传统的同步阻塞式编程模型已逐渐暴露出局限性。本文将深入探讨PHP中的异步编程模型,从早期的React到现代的Swoole,分析其原理、优势及应用场景,并通过实例展示如何利用这些工具提升PHP应用的性能和响应速度。文章旨在为PHP开发者提供一种全新的视角,帮助他们在构建高性能Web应用时做出更合理的技术选择。
56 0
|
30天前
|
前端开发 算法 JavaScript
React原理之Diff算法
【8月更文挑战第24天】
|
1月前
|
前端开发 JavaScript 算法
如何学习react原理
【8月更文挑战第9天】 如何学习react原理
37 6
|
28天前
|
前端开发 Java UED
瞬间变身高手!JSF 与 Ajax 强强联手,打造极致用户体验的富客户端应用,让你的应用焕然一新!
【8月更文挑战第31天】JavaServer Faces (JSF) 是 Java EE 标准的一部分,常用于构建企业级 Web 应用。传统 JSF 应用采用全页面刷新方式,可能影响用户体验。通过集成 Ajax 技术,可以显著提升应用的响应速度和交互性。本文详细介绍如何在 JSF 应用中使用 Ajax 构建富客户端应用,并通过具体示例展示 Ajax 在 JSF 中的应用。首先,确保安装 JDK 和支持 Java EE 的应用服务器(如 Apache Tomcat 或 WildFly)。
31 0
|
2月前
|
前端开发 JavaScript 算法
react【框架原理详解】JSX 的本质、SyntheticEvent 合成事件机制、组件渲染过程、组件更新过程
react【框架原理详解】JSX 的本质、SyntheticEvent 合成事件机制、组件渲染过程、组件更新过程
52 0
|
3月前
|
前端开发
前端React篇之React setState 调用的原理、React setState 调用之后发生了什么?是同步还是异步?
前端React篇之React setState 调用的原理、React setState 调用之后发生了什么?是同步还是异步?
|
3月前
|
前端开发 资源调度
如何本地 Debug React 源码
如何本地 Debug React 源码
32 3
|
4月前
|
前端开发 JavaScript 安全
React中的JSX:语法与原理深入解析
【4月更文挑战第25天】本文深入解析React中的JSX,一种JavaScript语法扩展,使代码更直观。JSX让开发者以HTML样式描述组件UI,但最终编译成JavaScript。通过Babel转换,JSX标签转为React.createElement()调用,创建虚拟DOM。JSX的优势在于直观性、类型安全、代码复用和工具支持,助力高效开发React组件,适应不断发展的Web应用需求。