React Fiber 任务分片 & 时间分片

源码基于react@16.13.1

Fiber 是一个工作单元,它的引入是react实现任务分片和时间分片的基础。分片是为了在Reconciliation阶段(纯js计算,无DOM操作)一点一点地执行任务,给浏览器喘息的机会,从而在体验上给用户更流畅的使用感受。

任务分片

一个工作单元是什么?可以从代码中直观地理解。

import React from 'react'
import ReactDOM from 'react-dom'

function App() {
    return (
        <div>
            <h1>Title</h1>
            <p>
                <a href='#'>link</a>
            </p>
        </div>
    )
}

ReactDOM.createRoot(document.getElementById('app')).render(<App />)

以上结构,被react分解成了6个fiber,也就是6个工作单元,如下图

react fiber

其中null对应着fiber的根节点,虽然在视觉上是什么都看不见的,但它的确在内存里。剩下的fiber节点都比较好理解。

每当更新发生时,react会从fiber的根节点开始,一个一个地循环遍历所有的fiber。

react 通过循环,一个一个地对fiber执行performUnitOfWork操作,以此实现了任务分片。

时间分片

时间分片的逻辑藏在循环里。

//react-reconciler -> ReactFiberWorkLoop.js
function workLoopConcurrent() {
  // Perform work until Scheduler asks us to yield
  while (workInProgress !== null && !shouldYield()) {
    workInProgress = performUnitOfWork(workInProgress);
  }
}

performUnitOfWork可能返回null或者下一个需要被执行的fiber,返回结果存在workInProgress中。workInProgress在react-reconciler模块中是全局变量。

当shouldYield返回true的时候,循环语句中断,一个时间分片就结束了,浏览器将重获控制权。

以下任意条件成立时,shouldYield会返回true

  • 时间片到期(默认5ms)
  • 更紧急任务插队

react 通过中断任务循环,实现了时间分片。

任务恢复

循环中断时,下一个未被完成的任务已经被保存到react-reconciler模块的全局变量workInProgress中。下一次循环开始时就从workInProgress开始。

跳出循环之后,react还做了一件事,通过MessageChannel发起了一个postMessage事件。

以上都发生在浏览器重获控制权之前。

而监听这个事件的,正是循环的发起者performWorkUntilDeadline

// scheduler.development.js
const performWorkUntilDeadline = () => {
    if (scheduledHostCallback !== null) {
      const currentTime = getCurrentTime();
      deadline = currentTime + yieldInterval;
      const hasTimeRemaining = true;
      try {
        // 通过scheduledHostCallback发起workLoopConcurrent循环
        const hasMoreWork = scheduledHostCallback(
          hasTimeRemaining,
          currentTime,
        );
        if (!hasMoreWork) {
          isMessageLoopRunning = false;
          scheduledHostCallback = null;
        } else {
          port.postMessage(null); // 发起postMessage事件
        }
      } catch (error) {
        port.postMessage(null);
        throw error;
      }
    } else {
      isMessageLoopRunning = false;
    }
    needsPaint = false;
  };

  const channel = new MessageChannel();
  const port = channel.port2;
  channel.port1.onmessage = performWorkUntilDeadline;

循环中断之后react执行 port.postMessage 发起了一个message事件,并且通过事件监听又恢复了循环。在循环中断到事件响应的间隙,浏览器重获了控制权,执行必要的渲染工作(如果有的话)。

以上特性,目前只在开启concurrent模式时有效。默认模式下只有任务分片,但是是同步执行的,所以不存在时间分片。


迅速搭建React源码测试环境

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。