文章目录
1. 前言
为了读代码更加有效率,提前看了一篇如何阅读源码的文章:https://zxc0328.github.io/2018/05/01/react-source-reading-howto/
因此此次本人阅读源码主要想看懂以下6个问题:
- ReactDOM.render()是如何挂载到真实DOM上的
- setState实现原理,为什么是异步的
- 生命周期结合2号问题一起看
- react16的fiber架构是什么
- jsx是如何解析的
- react hook是如何做到的
分析代码基于React V16.8.6,react源码目录截图如下图所示:
所需要看的代码一个库就够了,https://github.com/facebook/react/
先看的react-dom代码, 一点点单步到了scheduler
, 这个包的代码看起来不多(可能是我第一次看框架源码, 看的有点恶心… 一堆全局变量, 各个函数来回调用, 看了好几天, 如果下面哪里有问题, 还请各位同行指点), 那就先来梳理下这个包吧.
scheduler
这个包主要是在react做diff做任务分配机制, 核心机制类似于requestidlecallback,
window.requestIdleCallback()
会在浏览器空闲时期依次调用函数, 这就可以让开发者在主事件循环中执行后台或低优先级的任务,而且不会对像动画和用户交互这样延迟敏感的事件产生影响。
但这个函数支持度太惨
react则使用requestAnimationFrame
和postMessage
来模拟实现的requestidlecallback
. 工作原理是调度requestAnimationFrame
,存储帧开始的时间,然后调度postMessage
,后者在绘制后进行调度。
该包主要流程是把所有任务通过双向链表连接起来, 通过requestAnimationFrame
来在浏览器每帧的空闲时间循环处理所有任务, 直到链表为空为止.
2. getCurrentTime
这个函数后面会经常用到的, 先到前面来说下, 先看代码:
// packages\scheduler\src\forks\SchedulerHostConfig.default.js
const hasNativePerformanceNow =
typeof performance === 'object' && typeof performance.now === 'function';
const localDate = Date;
if (hasNativePerformanceNow) {
const Performance = performance;
getCurrentTime = function() {
return Performance.now();
};
} else {
getCurrentTime = function() {
// 该方法在 ECMA-262 第五版中被标准化, Date.now() === new Date().getTime();
// 出处 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Date/now#Compatibility
return localDate.now();
};
}
performance.now()
与 Date.now()
不同的是,返回了以微秒(百万分之一秒)为单位的时间,更加精准。
并且与 Date.now()
会受系统程序执行阻塞的影响不同,performance.now()
的时间是以恒定速率递增的,不受系统时间的影响(系统时间可被人为或软件调整)。
注意Date.now()
输出的是 UNIX 时间,即距离 1970 的时间,而performance.now()
输出的是相对于 time origin
(页面初始化: https://developer.mozilla.org/en-US/docs/Web/API/DOMHighResTimeStamp#The_time_origin) 的时间。
使用 Date.now()
的差值并非绝对精确,因为计算时间时受系统限制(可能阻塞)。但使用 performance.now() 的差值,并不影响我们计算程序执行的精确时间。
3. unstable_scheduleCallback函数
- 函数前面的unstable表示不稳定的意思, 之后还会有变动.
- 这个方法主要就是将任务组成双向链表, 并按照过期时间作为优先级.
先定义优先级, 代码如下:
// packages\scheduler\src\Scheduler.js
// Max 31 bit integer. The max integer size in V8 for 32-bit systems.
// 这是32位系统V8引擎里最大的整数
// Math.pow(2, 30) - 1
// 0b111111111111111111111111111111
var maxSigned31BitInt = 1073741823;
// Times out immediately 立即过期
var IMMEDIATE_PRIORITY_TIMEOUT = -1;
// Eventually times out
var USER_BLOCKING_PRIORITY = 250;
var NORMAL_PRIORITY_TIMEOUT = 5000;
var LOW_PRIORITY_TIMEOUT = 10000;
// Never times out
var IDLE_PRIORITY = maxSigned31BitInt;
主函数代码如下:
// packages\scheduler\src\Scheduler.js
// 组成双向链表, 开始安排任务
function unstable_scheduleCallback(priorityLevel, callback, deprecated_options) {
var startTime =
currentEventStartTime !== -1 ? currentEventStartTime : getCurrentTime();
// 过期时间 = 加入时间 + 优先级时间
var expirationTime;
if (
typeof deprecated_options === 'object' &&
deprecated_options !== null &&
typeof deprecated_options.timeout === 'number'
) {
expirationTime = startTime + deprecated_options.timeout;
} else {
// 根据不同的优先级, 赋予不同的过期时间
switch (priorityLevel) {
case ImmediatePriority:
expirationTime = startTime + IMMEDIATE_PRIORITY_TIMEOUT;
break;
case UserBlockingPriority:
expirationTime = startTime + USER_BLOCKING_PRIORITY;
break;
case IdlePriority:
expirationTime = startTime + IDLE_PRIORITY;
break;
case LowPriority:
expirationTime = startTime + LOW_PRIORITY_TIMEOUT;
break;
case NormalPriority:
default:
expirationTime = startTime + NORMAL_PRIORITY_TIMEOUT;
}
}
// 未完
上面先计算一下callback的过期时间, 接下来创建链表节点, 并组成链表, 代码如下:
// packages\scheduler\src\Scheduler.js
// 续上
// 基于上面的优先级和过期时间创建一个节点
var newNode = {
callback,
priorityLevel: priorityLevel,
expirationTime,
next: null,
previous: null,
};
if (firstCallbackNode === null) {
// This is the first callback in the list. 如果firstCallbackNode没有, 说明是第一个节点
firstCallbackNode = newNode.next = newNode.previous = newNode;
scheduleHostCallbackIfNeeded(); // 之后再说, 先忽略
} else {
var next = null;
var node = firstCallbackNode;
do {