探索 React 的内在 —— postMessage & Scheduler

postMessage & Scheduler

写在前面

  • 本文包含了一定量的源码讲解,其中笔者写入了一些内容来替代官方注释(就是写了差不多等于没写那种),若读者更青睐于原始的代码,

可移步官方仓库,结合起来阅读。也正因为这个原因,横屏或 PC 的阅读体验也许会更佳(代码可能需要左右滑动)

  • 本文没有显式的涉及 React Fiber Reconciler 和 Algebraic Effects(代数效应)的内容,但其实它们是息息相关的,可以理解为本文的内容就是实现前两者的基石。

有兴趣的读者可移步《Fiber & Algebraic Effects》做一些前置阅读。

开始

在去年 2019 年 9 月 27 日的 release 中,React 在 Scheduler 中开启了新的调度任务方案试验:

  • 旧方案:通过 requestAnimationFrame(以下统称 cAF,相关的 requestIdleCallback 则简称 rIC)使任务调度与帧对齐
  • 新方案:通过高频(短间隔)的调用 postMessage 来调度任务

Emm x1... 突然有了好多问题
那么本文就来探索一下,在这次“小小的” release 中都发生了什么

契机

通过对这次 release 的 commit-message 的查看,我们总结出以下几点:

  1. 由于 rAF 仰仗显示器的刷新频率,因此使用 rAF 需要看 vsync cycle(指硬件设备的频率)的脸色
  2. 那么为了在每帧执行尽可能多的任务,采用了 5ms 间隔的消息事件 来发起调度,也就是 postMessage 的方式
  3. 这个方案的主要风险是:更加频繁的调度任务会加剧主线程与其他浏览器任务的资源争夺
  4. 相较于 rAF 和 setTimeout,浏览器在后台标签下对消息事件进行了什么程度的节流还需要进一步确定,该试验是假设它与定时器有相同的优先级

简单来说,就是放弃了由 rAF 和 rIC 两个 API 构成的帧对齐策略,转而人为的控制调度频率,提升任务处理速度,优化 React 运行时的性能

postMessage


那么,postMessage 又是什么呢?是指 iframe 通信机制中的 postMessage 吗?

不对,也对

Emm x2... 好吧,有点谜语了,那解谜吧

不对

说不对呢,是因为 postMessage 本身是使用的 MessageChannel 这个接口创建的对象发起的

Channel Message API 的 MessageChannel 接口允许我们创建一个新的消息通道,并通过该通道的两个 MessagePort 进行通信

这个通道同样适用于 Web Worker —— 所以,它挺有用的...
我们看看它到底是怎样通信的:

const ch = new MessageChannel()

ch.port1.onmessage = function(msgEvent) {
  console.log('port1 got ' + msgEvent.data)
  ch.port1.postMessage('Ok, r.i.p Floyd')
}

ch.port2.onmessage = function(msgEvent) {
  console.log(msgEvent.data)
}

ch.port2.postMessage('port2!')

// 输出:
// port1 got port2!
// Ok, r.i.p Floyd.

很简单,没什么特别的...
Emm x3...
啊... 平常很少直接用它,它的兼容性怎么样呢?



唔!尽管是 10,但 IE 竟然也可以全绿!

也对

害,兼容性这么好,其实就是因为现代浏览器中 iframe 与父文档之间的通信,就是使用的这个消息通道,你甚至可以:

// 假设