【React的Fiber及中断-重启逻辑的设计】

React的Fiber

前言

react的循环渲染逻辑可以参考我的这篇文章。【react循环渲染

中断-重启的效果

以前的同步阻塞渲染的火焰峰图

现在开启中断-重启逻辑的火焰峰图

可以看到React的中断-重启分片的设计能够降低原本同步循环渲染带来的卡顿问题,分片的设计也使得React能够交换不同优先级任务间的执行顺寻。不过这也为React引入了另一个问题,并发渲染导致的同一页面不同组件在同一帧读取到不同store值的问题(react提供的解决方式useSyncExternalStore)。

中断-重启逻辑

入口

前言的文章即是按照同步阻塞渲染逻辑来debugger的。中断-重启分片渲染逻辑与阻塞渲染逻辑分道扬镳始于这个判断(因本人不知道如何才能让react自动走到这个判断上来,索性debugger的时候直接把shouldTimeSlice改成true了)

typescript 复制代码
// 中断-重启调用栈
performConcurrentWorkOnRoot -> renderRootConcurrent -> workLoopConcurrent -> performUnitOfWork -> beginWork
// 相较于同步循环渲染区别就在于workLoopConcurrent的调用
function workLoopConcurrent() {
  // Perform work until Scheduler asks us to yield
  while (workInProgress !== null && !shouldYield()) {
    performUnitOfWork(workInProgress);
  }
}
// 通过额外添加!shouldYield()来增加跳出循环的时机,释放栈给其它任务然后再在某一时候重启中断的循环

shouldYield()函数会在执行时间超过5ms的时候返回true进而跳出当前的while循环

typescript 复制代码
function shouldYield () {
	const timeElapsed = performance.now() - startTime
	if (timeElapsed  < 5) {
		return false
	}
	return true
}

中断

通过控制shouldYield函数返回true来跳出workLoopConcurrent的while循环,进而进入为之后重启做准备的逻辑。

typescript 复制代码
// 进入中断流程之后renderRootConcurrent函数的返回值
function renderRootConcurrent() {
	if (workInProgress !== null) {
		return RootInProgress
	} else {
		return workInProgressRootExitStatus; // 返回完成状态
	}
}
// 如果返回的是RootInProgress则会进入后续的重启准备中。像同步流程中结束之后返回workInProgressRootExitStatus,则会进入finishConcurrentRender函数开启commitRoot(虚拟dom到真实dom的创建渲染过程)

调试项目中的自定义组件,主要是展示与上方FiberNode中的type关系,非流程相关的图片,可以忽略不看

重启准备

从入口部分粘贴的代码部分中可以看到performConcurrentWorkOnRoot函数即为终端重启调用栈的开始,这里的返回为下一次的开启创造了函数执行的条件

此时函数栈弹出到workLoop函数中,并将continuationCallback赋值成上面返回的函数,最后赋值给currentTask.callback,并在下一次的while循环通过shouldYieldToHost()进行循环的跳出

typescript 复制代码
function workLoop() {
	while() {
		if(shouldYieldToHost()) {
			break; // 通过此处跳出上述循环
		}
		const callback = currentTask.callback; // 任务队列中当前任务的执行函数
		const continuationCallback = callback(); // 任务执行完成后的返回值
		if (typeof continuationCallback === 'function') {
			currentTask.callback = continuationCallback;
		}
	}
	if (currentTask !== null) {
		return true
	} else {
		return false
	}
}


中断到准备重启函数调用栈弹出顺序

typescript 复制代码
// ---> 代表函数栈弹出方向
workLoopConcurrent ---> renderRootConcurrent -> performConcurrentWorkOnRoot -> flushWork -> performWorkUntilDeadline
// 弹出到performWorkUntilDeadline会执行schedulePerformWorkUntilDeadline()
const performWorkUntilDeadline = () => {
	let hasMoreWork = true;
	try {
		hasMoreWork = scheduledHostCallback(hasTimeRemaining, currentTime);
	} finally {
		 if (hasMoreWork) {
        schedulePerformWorkUntilDeadline();
      } else {
        isMessageLoopRunning = false;
        scheduledHostCallback = null;
      }
	}
}

// schedulePerformWorkUntilDeadline 是一个全局let变量
 schedulePerformWorkUntilDeadline = () => {
   port.postMessage(null); // 此时会推送一个为null的消息,等待下一次时间循环被取出
 };

MessageChannel(扩展)

扩展React这里的port使用的是什么消息发布订阅功能MessageChannel)。port.postMessage()可以任务具有宏任务的作用,会遵循事件循环来等待任务的调用。

typescript 复制代码
const { port1, port2 } = new MessageChannel();
port1.onmessage = (event) => {
    console.log("port1收到消息", event);
};
import React from "react";
const MessageChannelIndex = () => {
    function testEventLoop () {
        console.log("我是事件循环同步任务的开始");
        setTimeout(() => {
            console.log("我是setTimeout");
        }, 0);
        port2.postMessage("我是port2");
        console.log("我是事件循环同步任务的结束");
    }
    return (
        <div>
            <button onClick={testEventLoop}>开始</button>
        </div>
    );
};

export default MessageChannelIndex;

看下打印效果

宏任务可以让浏览器能够调度高优先级的像用户输入,滚动,I/O等高优先级的任务进行插队,从而防止卡顿。

重启

当事件循环来到port1.onmessage订阅的函数执行时,循环渲染变可以开始重启了。

typescript 复制代码
// 订阅函数
channel.port1.onmessage = performWorkUntilDeadline
// 开始循环渲染的调用栈顺序
performWorkUntilDeadline -> flushWork -> workLoop -> performConcurrentWorkOnRoot -> renderRootConcurrent -> workLoopConcurrent

结语

按照上述流程循环往复知道全部渲染完成。

相关推荐
2601_949833393 分钟前
flutter_for_openharmony口腔护理app实战+饮食记录实现
android·javascript·flutter
2601_9494800611 分钟前
【无标题】
开发语言·前端·javascript
css趣多多16 分钟前
Vue过滤器
前端·javascript·vue.js
●VON1 小时前
React Native for OpenHarmony:项目目录结构与跨平台构建流程详解
javascript·学习·react native·react.js·架构·跨平台·von
爱吃大芒果1 小时前
Flutter for OpenHarmony 实战:mango_shop 路由系统的配置与页面跳转逻辑
开发语言·javascript·flutter
qq_177767371 小时前
React Native鸿蒙跨平台实现消息列表用于存储所有消息数据,筛选状态用于控制消息筛选结果
javascript·react native·react.js·ecmascript·harmonyos
沐雪架构师1 小时前
LangChain 1.0 Agent开发实战指南
开发语言·javascript·langchain
2501_940007892 小时前
Flutter for OpenHarmony三国杀攻略App实战 - 战绩记录功能实现
开发语言·javascript·flutter
摘星编程2 小时前
React Native + OpenHarmony:自定义useEllipsis省略号处理
javascript·react native·react.js
这是个栗子2 小时前
【Vue代码分析】前端动态路由传参与可选参数标记:实现“添加/查看”模式的灵活路由配置
前端·javascript·vue.js