看了最新的代码,发现看不懂,最新的引入了lane替代之前的expirtionTime,能不能花个时间简单出个文章,给个引导提示

来源:1-1 课程导学

慕圣4455838

2020-06-24

看了最新的代码,发现看不懂,最新的引入了lane替代之前的expirtionTime,能不能花个时间简单出个文章,给个引导提示

写回答

2回答

大手冰凉3258617

2020-07-08

看来是react 团队有意用一种称为Lanes的新的优先级模式替换原有的expirationTime的优先级模式,最新版本的代码已经将原有模式都替换掉了,包括Scheduler内部关于优先级的实现。目前网上包括国外社区我没找到Lanes的介绍,但官方仓库有commit以及讨论:

https://github.com/facebook/react/commit/93e078ddf274636b0a40bd5501ce3549aec700fa

https://github.com/facebook/react/pull/18796

第二个pr中的Lanes简介我翻译了比较主要的部分,贴在下边。源码以及更新机制我也正在看,可能有些地方不准确,仅供参考吧。等理解了源码后,会整理出关于Lanes的文章。

与过期时间相比,Lanes模型有两个主要优势

  • lane将任务优先级(“任务的A优先级是否高于任务B?”)从任务批处理(“任务A是这组任务的一部分吗?”)的概念中解耦出来。

  • lane可以用一个32位的数据类型表示许多不同的任务线程。

下方示例代码是取自ReactFiberLane.js,功能是在root节点上的待处理优先级(pendingLanes
)标记为过期(expiredLanes )。概念上可理解为将待处理的优先级放到过期的优先级集合中去,是Lanes模型位运算的实际运用场景之一。

export function markRootExpired(root: FiberRoot, expiredLanes: Lanes) {
  /**
   * root.expiredLanes |= expiredLanes & root.pendingLanes
   * 这种形式相当于 root.expiredLanes = root.expiredLanes | (expiredLanes & root.pendingLanes)
   * 目的是将pendingLanes 合并到 root.expiredLanes 中
   *
   * 例如:
                                      expiredLanes = 0101
                                 root.pendingLanes = 0100
   const result = expiredLanes & root.pendingLanes = 0100

                                 root.expiredLanes = 0010
    root.expiredLanes = root.expiredLanes | result = 0110
   * */
  root.expiredLanes |= expiredLanes & root.pendingLanes;
}

在旧模型中,为了确定正在进行的批处理中是否包含指定的工作单元,不得不比较该工作单元与批处理的相对优先级:

const isTaskIncludedInBatch = priorityOfTask >= priorityOfBatch;

之所以这种方式可以达到目的,是因为我们是施加了一个约束,即在完成高优先级的任务之前,不允许处理较低优先级的任务。假设优先级 A > B > C,你不能在没有A的情况下处理B,如果没有A和B,你也不能处理C。

这种规则是在有任务挂起之前出现的,在这种场景里是有意义的,即当所有任务都是CPU相关的任务时,必须按照优先级来处理任务。但是,当你引入了IO相关的任务(即挂起)时,可能会遇到这样的场景:高优先级的IO任务阻塞了低优先级的CPU相关任务的完成。

过期时间的一个缺陷是,它限制了我们表达一组多个优先级级别的方式。

无论从内存角度还是计算角度来看,使用Set对象都是不切实际的。这种优先级的检查非常多,所以它们需要速度快,使用尽可能少的内存。

作为一种妥协,我们通常会做的是保持优先级的范围:

const isTaskIncludedInBatch = taskPriority <= highestPriorityInRange && taskPriority >= lowestPriorityInRange;

但这种方式不是十全十美的,可以用它来标识一个封闭、连续的任务范围,但并不能区分出这个范围内的某个任务。例如,指定一个任务范围,如何删除一个位于该范围中间的任务呢?即使已经有了一个不错的解决方案,用这种方式来寻找目标任务也会变得混乱,并容易出现递归。(“并容易出现递归”可能翻译得不准确,但表达的意思是这种方式非常复杂)

旧模型将优先级和批处理这两个概念结合成一个单一的数据类型。

在新的模型中,我们已经将这两个概念解耦了。任务组不是用相对数字表示,而是用位掩码表示:

const isTaskIncludedInBatch = (task & batchOfTasks) !== 0;

(注: task & batchOfTasks 为位掩码运算(按位与 &),检查batchOfTasks中是否含有task)

表示任务的位掩码类型称为Lane。表示批处理的位掩码的类型称为Lanes。

(注:实际上无论 Lane 或者 Lanes类型,都是number类型)

更具体地说,由setState调度的更新对象包含一个lane字段,它是一个启用了单个位的位掩码。这将替换旧模型中update的expirationTime字段。

另一方面,一个fiber并不只与单个更新相关联,而可能关联到多个更新。因此它有一个lane字段,一个启用零位或更多位的位掩码(旧模型中的fiber.expirationTime);和一个childLanes字段(fiber.childExpirationTime)。

Lanes是一种不透明类型。你只能在ReactFiberLane模块中执行直接的位掩码操作。在其他地方,必须从该模块导入相关的函数。这是一种权衡,但我认为它最终是值得的,因为处理lane可能非常微妙,并且同步所有逻辑将使我们更容易调整我们的代码,而不必每次都重构。

常见的过期时间字段,将转换为Leans

  • renderExpirationtime -> renderLanes

  • update.expirationTime -> update.lane

  • fiber.expirationTime -> fiber.lanes

  • fiber.childExpirationTime -> fiber.childLanes

  • root.firstPendingTime and root.lastPendingTime -> fiber.pendingLanes


2
0

Jokcy

2020-06-25

好的,我了解一下情况看一下

0
0

React源码深度解析 高级前端工程师必备技能

掌握React源码,让你的开发水平没有上限,更不惧前端未来的到来

1751 学习 · 336 问题

查看课程