- 最近比较空闲, 以前对React的了解总是有限, 故打算最近把源码给看了
- 阅读代码的思路会先直接通过打断点+github拉取源码的方式综合阅读, 从头开始把React整个执行链路大概搞清楚, 这个方面会分成四个部分进行
- 后面看情况,根据模块细分然后再细看源码
源码和运行代码都是使用的React18.2.0
一、总览
从react拉下的目录有很多个, 我们主要关注的是packages
目录, 其中又有一些比较核心的包, 我们找到的关键代码基本都来自以下包, 例如
sh
|-- react # 一些核心通用的API例如useState, useRef
|-- react-dom # dom相关的源码
|-- react-recociler # Fiber相关代码
|-- schduler # 调度相关代码
先来个简单的代码,然后直接开启调试。 以index.js
作为入口, 其中又调用了createRoot
方法, 传入了#root
的DOM
对象, 然后返回一个root
对象, 再调用render
方法
jsx
// index.js
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
// App.js
import { useState } from 'react';
import { useState } from 'react';
function App() {
const [count, setCount] = useState();
const changeCount = () => {
setCount(count => count + 1);
}
return (
<div>
<div onClick={changeCount}>{count}</div>
</div>
);
}
export default App;
二、 找到入口
我们先从ReactDOM.createRoot()
方法入手, 先来探究他做了些什么事情
- 从代码中可以看到他就是直接调用了
createRootImpl
js
export function createRoot(
container: Element | Document | DocumentFragment,
options?: CreateRootOptions,
): RootType {
....
return createRootImpl(container, options);
}
- 再看
createRootImpl
, 先通过nodeType
判断了传入的是否为有效DOM, 然后根据options
和container
生成对应的FiberRootNode
应用根对象, 然后再在#Root
上监听了所有事件, 最后根据FiberRootNode
创建了一个ReactDOMRoot
返回
js
export function createRoot(
container: Element | Document | DocumentFragment,
options?: CreateRootOptions,
): RootType {
// 通过nodeType判断是否为有效DOM元素
if (!isValidContainer(container)) {
throw new Error('createRoot(...): Target container is not a DOM element.');
}
...
// 创建对的FiberRootNode
// 其内部调用了createFiberRoot->createHostRootFiber->createFiber
const root = createContainer(
container,
ConcurrentRoot,
null,
isStrictMode,
concurrentUpdatesByDefaultOverride,
identifierPrefix,
onRecoverableError,
transitionCallbacks,
);
// 内部通过node[internalContainerInstanceKey] = hostRoot;进行标记
// 而内部又定义了const internalContainerInstanceKey = '__reactContainer$' + randomKey;
// 故就是给#root这个DOM元素设置了一个内部的属性, 存储了生成的FiberRootNode的current对象
markContainerAsRoot(root.current, container);
Dispatcher.current = ReactDOMClientDispatcher;
const rootContainerElement: Document | Element | DocumentFragment =
container.nodeType === COMMENT_NODE
? (container.parentNode: any)
: container;
// 监听所有事件
listenToAllSupportedEvents(rootContainerElement);
// $FlowFixMe[invalid-constructor] Flow no longer supports calling new on functions
return new ReactDOMRoot(root);
}
- 我们可以再细看一下
createContainer
调用链路, 毕竟是整个Fiber结构的开始 - 逻辑上也很清晰
- 首先生成了
FiberRoot
和uninitializedFiber
(也就是hostFiberRoot
) - 然后让彼此关联起来(通过
current
和stateNode
指针) - 再对
uninitializedFiber
上的memoizedState
和updateQueue
进行初始化
- 首先生成了
生成的这两玩意都挺重要, 后面多次使用
js
export function createFiberRoot(
containerInfo: Container,
tag: RootTag,
hydrate: boolean,
initialChildren: ReactNodeList,
hydrationCallbacks: null | SuspenseHydrationCallbacks,
isStrictMode: boolean,
concurrentUpdatesByDefaultOverride: null | boolean,
identifierPrefix: string,
onRecoverableError: null | ((error: mixed) => void),
transitionCallbacks: null | TransitionTracingCallbacks,
): FiberRoot {
// 创建FiberRootNode应用根节点
const root: FiberRoot = (new FiberRootNode(
containerInfo,
tag,
hydrate,
identifierPrefix,
onRecoverableError,
): any);
....
// 创建hostRootFiber, Fiber树的根节点
const uninitializedFiber = createHostRootFiber(
tag,
isStrictMode,
concurrentUpdatesByDefaultOverride,
);
// 将两者关联
root.current = uninitializedFiber;
uninitializedFiber.stateNode = root;
.....
// 初始化hostRootFiber上的memoizedState
const initialState: RootState = {
element: initialChildren,
isDehydrated: hydrate,
cache: (null: any), // not enabled yet
};
uninitializedFiber.memoizedState = initialState;
// 初始化hostRootFiber的updateQueue属性
initializeUpdateQueue(uninitializedFiber);
// 返回Root
return root;
}
-
调用
new FiberRootNode
拿到的Root
根应用对象, 其中有一些属性还是值得了解一下, 在后面都会接触到current
: 指向当前的FibercontainerInfo
: 指传入的DOM元素pendingLanes
: 待更新任务优先级suspendedLanes
:挂起任务优先级pingedLanes
: 需要重新调度的更新任务优先级evnetTime
: 任务启动时间数组, 初始化每一项都为0expirationTimes
: 任务过期时间数组, 初始化每一项都为-1
-
调用
createHostRootFiber
内部又是调用了createFiber
->new FiberNode
, 其实就是生成了一个Fiber
节点, 一些关键的属性也值得了解一下tag
: 节点类型key
: 节点Keyreturn
: 指向父节点sibling
: 指向下一个兄弟节点child
:指向第一个子节点pendingProps
: 等待处理的propsmemoizedProps
: 存储的propsupdateQueue
: 存储更新对象链表memoizedState
: 存储hook链表mode
: 模型lanes
: 优先级调度childLanes
: 子节点的优先级dependencies
: 保存context, 事件相关内容flags
: 副作用标记
- 以上描述的逻辑如图所示
三、启动
接着我们可以从调用栈入手, 可以看到加载完src/index.js
之后, 调用render
函数-> updateContainer
函数-> scheduleUpdateOnFiber
函数 -> ensureRootIsScheduled
函数 -> schedulePerformWorkUntilDeadline
函数-> performWorkUntilDeadline
-> flushWork
函数 -> workLoop
函数 -> renderRootSync
函数 -> workLoopSync
函数 -> performUnitOfWork
函数 -> beginWork
函数-> ...................
我们将上述的函数(以及一些这里还没有走到的函数)分成四个部分分别分析一通, 接下去的几篇文章的目录也是基于这个展开。 可供快速定位
- 入口初始化过程
- render()
- updateContainer()
- Scheduler: 根据优先级以及时间分片对任务进行调度
- scheduleUpdateOnFiber()
- ensureRootIsScheduled()
- unstable_scheduleCallback()
- requestHostCallback()
- schedulePerformWorkUntilDeadline()
- performWorkUntilDeadline()
- flushWork()
- workLoop()
- performConcurrentWorkOnRoot()
- Reconciler: FiberTree的构建生成
- renderRootSync()
- workLoopSync()
- performUnitOfWork()
- beiginWork()
- uppateHostRoot()
- reconcileChildFibers()
- completeUnitOfWork()
- completeWork()
- Commit: 根据Reconciler阶段创建的Fiber树,构建真实的DOM树
- finistConcurrentRender()
- commitRootImpl()
- flushPassiveEffects()
- commitBeforeMutationEffects()
- commitMutationEffects()
- commitLayoutEffects()
这一篇,我们先把初始化过程给搞定
四、入口初始化
这个过程主要就两个函数, 一个是作为入口的rende()
函数, 一个是updateContainer(
)函数
render函数
- 从上面的
createRoot()
函数我们可以知道, 最后返回的是ReactDOMRoot
的实例, 且在实例化的时候我们只实例了一个属性,也就是_internalRoot
。 那么我们的render
方法自然是通过prototype
挂载在原型链上 - 这里只对一些情况做报错处理,然后内部直接调用了
updateContainer
js
ReactDOMRoot.prototype.render =
// $FlowFixMe[missing-this-annot]
function (children: ReactNodeList): void {
const root = this._internalRoot; // 拿到我们生成的RootFiberNode
if (root === null) {
throw new Error('Cannot update an unmounted root.');
}
const container = root.containerInfo; // 拿到传入的DOM元素
....
updateContainer(children, root, null, null);
};
updateContainer函数
- 先来结论
- 获取当前Fiber节点的优先级
lane
- 根据
eventTime
和lane
创建update
对象 - 将
update
对象合并入Fiber的updateQueue
中 - 然后调用
scheduleUpdateOnFiber
调度更新Fiber
- 逐步分析
- 整体代码如下, 可以分别对照上述结论
js
export function updateContainer(
element: ReactNodeList,
container: OpaqueRoot,
parentComponent: ?React$Component<any, any>,
callback: ?Function,
): Lane {
// container值得是FiberRootNode, current拿出其中的HostFiber
const current = container.current;
// 获取当前时间
const eventTime = requestEventTime();
// 获取到当前Fiber优先级lane
const lane = requestUpdateLane(current);
...
// 获取父组件的上下文
const context = getContextForSubtree(parentComponent);
// 处理上下文
if (container.context === null) {
container.context = context; // 继承上下文
} else {
container.pendingContext = context; // 记录等待上下文
}
// 创建更新对象
const update = createUpdate(eventTime, lane);
// 设置payload
update.payload = { element };
if (callback !== null) {
....
update.callback = callback; // 初始化callback
}
....
//将更新对象update推入栈中
const root = enqueueUpdate(current, update, lane);
if (root !== null) {
// 开启调度任务
scheduleUpdateOnFiber(root, current, lane, eventTime);
entangleTransitions(root, current, lane);
}
return lane;
}
其中也有比较重要的执行过程, 其中涉及到优先级相关概念, 故我们先了解一波优先级,然后再回看该函数
五、优先级
优先级的出现就肯定有高低的比较, 目的是让高优先级的任务可以先执行, 低优先级的任务延后执行。 在React中我们常用到三种优先级。
优先级分类
lane
优先级, 一共有31种优先级
js
export const TotalLanes = 31;
export const NoLanes: Lanes = /* */ 0b0000000000000000000000000000000;
export const NoLane: Lane = /* */ 0b0000000000000000000000000000000;
export const SyncLane: Lane = /* */ 0b0000000000000000000000000000001;
export const InputContinuousHydrationLane: Lane = /* */ 0b0000000000000000000000000000010;
export const InputContinuousLane: Lane = /* */ 0b0000000000000000000000000000100;
export const DefaultHydrationLane: Lane = /* */ 0b0000000000000000000000000001000;
export const DefaultLane: Lane = /* */ 0b0000000000000000000000000010000;
const TransitionHydrationLane: Lane = /* */ 0b0000000000000000000000000100000;
const TransitionLanes: Lanes = /* */ 0b0000000001111111111111111000000;
const TransitionLane1: Lane = /* */ 0b0000000000000000000000001000000;
const TransitionLane2: Lane = /* */ 0b0000000000000000000000010000000;
const TransitionLane3: Lane = /* */ 0b0000000000000000000000100000000;
const TransitionLane4: Lane = /* */ 0b0000000000000000000001000000000;
const TransitionLane5: Lane = /* */ 0b0000000000000000000010000000000;
const TransitionLane6: Lane = /* */ 0b0000000000000000000100000000000;
const TransitionLane7: Lane = /* */ 0b0000000000000000001000000000000;
const TransitionLane8: Lane = /* */ 0b0000000000000000010000000000000;
const TransitionLane9: Lane = /* */ 0b0000000000000000100000000000000;
const TransitionLane10: Lane = /* */ 0b0000000000000001000000000000000;
const TransitionLane11: Lane = /* */ 0b0000000000000010000000000000000;
const TransitionLane12: Lane = /* */ 0b0000000000000100000000000000000;
const TransitionLane13: Lane = /* */ 0b0000000000001000000000000000000;
const TransitionLane14: Lane = /* */ 0b0000000000010000000000000000000;
const TransitionLane15: Lane = /* */ 0b0000000000100000000000000000000;
const TransitionLane16: Lane = /* */ 0b0000000001000000000000000000000;
const RetryLanes: Lanes = /* */ 0b0000111110000000000000000000000;
const RetryLane1: Lane = /* */ 0b0000000010000000000000000000000;
const RetryLane2: Lane = /* */ 0b0000000100000000000000000000000;
const RetryLane3: Lane = /* */ 0b0000001000000000000000000000000;
const RetryLane4: Lane = /* */ 0b0000010000000000000000000000000;
const RetryLane5: Lane = /* */ 0b0000100000000000000000000000000;
event
优先级,分为四种, 分别指向不同的优先级
js
// 离散事件优先级, 为同步优先级0b0000000000000000000000000000010
// 比如click,input, change, blur, focus等
export const DiscreteEventPriority: EventPriority = SyncLane;
// 连续事件优先级, 为输入持续优先级0b0000000000000000000000000001000
// 比如touchmove, scroll,dragenter等
export const ContinuousEventPriority: EventPriority = InputContinuousLane;
// 默认事件优先级, 为0b0000000000000000000000000100000
export const DefaultEventPriority: EventPriority = DefaultLane;
// 空闲事件优先级, 为0b0100000000000000000000000000000
export const IdleEventPriority: EventPriority = IdleLane;
scdeluar
优先级
js
let ReactPriorityLevels: ReactPriorityLevelsType = {
ImmediatePriority: 99,
UserBlockingPriority: 98,
NormalPriority: 97,
LowPriority: 96,
IdlePriority: 95,
NoPriority: 90,
};
优先级转换
lane
优先级转为event
优先级, 通过lanesToEventPriority
event
优先级转换为scheduler
优先级, 具体逻辑在ensureRootIsScheduled
, 后面会涉及对该函数的讲解
优先级的操作
优先级的各种操作都是通过位运算进行的
-
合并
- 当遇到多个优先级时,往往需要合并到lanes进行处理
- 采用的是位获计算。
- 在很多地方都能看到该用法, 例如(在后面都会涉及)
markRootUpdated
更新pendingLanes
:root.pendingLanes |= updateLane;
markStarvedLanesAsExpired
更新expiredLanes
:root.expiredLanes |= lane;
js01000 | 00100 = 01100
-
释放
- 当执行完或者计算完需要释放相关优先级时,此时也需要对lanes进行处理
- 采用的是位与和位非
- 例如:
- 在
markStarvedLanesAsExpired
处理lanes的时候, 当前lanes
处理完之后就会采用lanes &= ~lane;
释放, 以便下一个循环能够拿到下一个优先级
- 在
js11000 & ~10000 -> 11000 & 01111 = 01000
-
拿到最高优先级
- 这个React直接写了一个函数
- 采用的是位与和符号位取反
jsexport function getHighestPriorityLane(lanes: Lanes): Lane { return lanes & -lanes; }
- 使用到他的地方就很多了, 包括上面讲述的
lane
和event
优先级的转换ensureRootIsScheduled
用它找出最高级lane
来表示回调函数的优先级
js10001 & -10001 = 00001
六、 updateContainer
了解完优先级我们继续回到updateContainer
requestUpdateLane
updateContainer
中通过调用requestUpdateLane
拿到优先级requestUpdateLane
代码如下
js
export function requestUpdateLane(fiber: Fiber): Lane {
// 获取到当前渲染的模式
const mode = fiber.mode;
// 通过按位与的方式判断是否并发模式
if ((mode & ConcurrentMode) === NoMode) {
return (SyncLane: Lane); // 非并发的话优先级都为同步优先级
// workInProgressRootRenderLanes是需要更新的fiber节点上的lane的值
// 不为空的时候则说明当前有任务在执行
} else if (
(executionContext & RenderContext) !== NoContext &&
workInProgressRootRenderLanes !== NoLanes ) {
// 这里又调用了getHighestPriorityLane,上面我们描述过;了,就是为了找最高优先级
return pickArbitraryLane(workInProgressRootRenderLanes);
}
// 检查当前事件是否是过渡优先级
const isTransition = requestCurrentTransition() !== NoTransition;
if (isTransition) {
.......
// 拿到当前的过渡任务优先级,然后左移一位,过渡优先级一共有16种, 如果16种都用完了就重开
const actionScopeLane = peekEntangledActionLane();
return actionScopeLane !== NoLane ? : requestTransitionLane();
}
// 此次更新的优先级
const updateLane: Lane = (getCurrentUpdatePriority(): any);
if (updateLane !== NoLane) {
return updateLane;
}
// 此时事件的优先级
// 内部使用了switch case对不同的event获取不同的优先级, 都没命中的话则走到
// default为默认优先级16
const eventLane: Lane = (getCurrentEventPriority(): any);
return eventLane;
}
createUpdate
- 在
updateContainer
中,update
对象是通过createUpdate()
创建的
js
export function createUpdate(lane: Lane): Update<mixed> {
const update: Update<mixed> = {
lane, // 优先级
tag: UpdateState, // 0
payload: null, // 更新内容, updateContainer还会进行赋值操作
callback: null, // 回调,updateContainer还会进行赋值操作
next: null, // 通过next指向下一个update对象形成链表
};
return update;
}
enqueueUpdate
- 在
updateContainer
中update
对象是通过enqueueUpdate()
加入更新队列的, 其中又调用了enqueueConcurrentClassUpdate
js
export function enqueueUpdate<State>(
fiber: Fiber,
update: Update<State>,
lane: Lane,
): FiberRoot | null {
const updateQueue = fiber.updateQueue;
if (updateQueue === null) {
// Only occurs if the fiber has been unmounted.
return null;
}
// 拿到Fiber.updateQueue.shared
const sharedQueue: SharedQueue<State> = (updateQueue: any).shared;
// 判断是否为不安全渲染
if (isUnsafeClassRenderPhaseUpdate(fiber)) {
// 涉及类组件的UNSAFE_componentWillReceive才会走到这
const pending = sharedQueue.pending;
if (pending === null) { // 开头
update.next = update;
} else {
update.next = pending.next;
pending.next = update;
}
sharedQueue.pending = update;
return unsafe_markUpdateLaneFromFiberToRoot(fiber, lane);
} else {
// 基本上都是走这个分支
return enqueueConcurrentClassUpdate(fiber, sharedQueue, update, lane);
}
}
- 不安全渲染模式的更新如图所示
enqueueConcurrentClassUpdate
- 基本上都是记录当前
update
的相关信息, 推入全局的concurrentQueue
中.
js
export function enqueueConcurrentClassUpdate<State>(
fiber: Fiber,
queue: ClassQueue<State>,
update: ClassUpdate<State>,
lane: Lane,
): FiberRoot | null {
const concurrentQueue: ConcurrentQueue = (queue: any);
const concurrentUpdate: ConcurrentUpdate = (update: any);
enqueueUpdate(fiber, concurrentQueue, concurrentUpdate, lane);
return getRootForUpdatedFiber(fiber);
}
// concurrentQueuesIndex是全局维护的一个下标, concurrentQueues是全局维护的栈
let concurrentQueuesIndex = 0;
const concurrentQueues: Array<any> = [];
let concurrentlyUpdatedLanes: Lanes = NoLanes;
function enqueueUpdate(
fiber: Fiber,
queue: ConcurrentQueue | null,
update: ConcurrentUpdate | null,
lane: Lane,
) {
// 下标做递增操作, 然后把相关的数据放入全局栈中
concurrentQueues[concurrentQueuesIndex++] = fiber;
concurrentQueues[concurrentQueuesIndex++] = queue;
concurrentQueues[concurrentQueuesIndex++] = update;
concurrentQueues[concurrentQueuesIndex++] = lane;
// 做合并操作
concurrentlyUpdatedLanes = mergeLanes(concurrentlyUpdatedLanes, lane);
// 更新fiber上的lanes
fiber.lanes = mergeLanes(fiber.lanes, lane);
const alternate = fiber.alternate;
if (alternate !== null) {
alternate.lanes = mergeLanes(alternate.lanes, lane);
}
}
这一部分需要了解的东西很少,主要是初步对源码进行了划分、然后了解了相关的基本概念。
处理完更新对象之后, 就走到了scheduleUpdateOnFiber(root, current, lane);
, 进入了我们的第二阶段, 这部分内容放在第二篇文章进行讲解