react源码学习之首次渲染创建更新的流程记录
/ / 点击 / 阅读耗时 16 分钟之前知道了首次渲染主要数据结构的构建过程,现在进入创建更新的过程(下图中的右侧部分)。
graph TD; render --> legacyRenderSubtreeIntoContainer; legacyRenderSubtreeIntoContainer --> legacyCreateRootFromDOMContainer; legacyRenderSubtreeIntoContainer --> unbatchedUpdates; unbatchedUpdates --> updateContainer; updateContainer --> requestCurrentTimeForUpdate; requestCurrentTimeForUpdate --> msToExpirationTime; msToExpirationTime --> getCurrentTime; updateContainer --> requestCurrentSuspenseConfig; updateContainer --> computeExpirationForFiber; updateContainer --> getContextForSubtree; updateContainer --> createUpdate; updateContainer --> enqueueUpdate; updateContainer --> scheduleUpdateOnFiber;
函数名 | 参数(类型) | 位置 |
---|---|---|
unbatchedUpdates | fn(Function) | react-reconciler/src/ReactFiberWorkLoop |
updateContainer | element(ReactNodelist)、container(OpaqueRoot)、parentComponent(React$Component)、callback(Function) | react-reconciler/src/ReactFiberReconciler |
requestCurrentTimeForUpdate | react-reconciler/src/ReactFiberWorkLoop | |
msToExpirationTime | ms(number) | react-reconciler/src/ReactFiberExpirationTime |
getCurrentTime | - | scheduler/src/forks/SchedulerHostConfig.default |
requestCurrentSuspenseConfig | - | react-reconciler/src/ReactFiberSuspenseConfig |
computeExpirationForFiber | currentTime(ExpirationForFiber)、fiber、suspenseConfig(null 或 SuspenseConfig) | react-reconciler/src/ReactFiberWorkLoop |
getContextForSubtree | parentComponent(ReactComponent) | react-reconciler/src/ReactFiberReconciler |
createUpdate | expirationTime(ExpirationTime)、suspenseConfig(null 或 SuspenseConfig) | react-reconciler/src/ReactUpdateQueue |
enqueueUpdate | fiber(Fiber)、update(Update) | react-reconciler/src/ReactUpdateQueue |
函数调用
unbatchedUpdates
这个函数内部代码描述见参考1
简化后的代码如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23// const NoContext = 0b0000000;
// const BatchedContext = 0b0000001;
// const EventContext = 0b0000010;
// const DiscreteEventContext = 0b0000100;
// const LegacyUnbatchedContext = 0b0001000;
// const RenderContext = 0b0010000;
// const CommitContext = 0b0100000;
// const RetryAfterError = 0b1000000;
function unbatchedUpdates(fn, a) {
const prevExecutionContext = executionContext;
executionContext &= ~BatchedContext;
executionContext |= LegacyUnbatchedContext;
try {
return fn(a);
} finally {
executionContext = prevExecutionContext;
if (executionContext === NoContext) {
// Flush the immediate callbacks that were scheduled during this batch
flushSyncCallbackQueue();
}
}
}进入try代码块代码的执行顺序:
- 执行fn(a)
- 执行finally中的内容
- 返回fn(a)的执行结果
executionContext这个变量Describes where we are in the React execution stack,变换规则(见参考2):
- 将当前上下文添加 render:executionContext |= RenderContext
- 判断当前是否处于render(感觉这里是否处于并不准确因为可能添加了多个context,这个后面再来修补概念): executionContext & RenderContext !== noContext
- 去除 render: executionContext &= ~RenderContext
这里先去除BatchedContext状态,又置为LegacyUnbatchedContext盲猜是为其他地方或是fn的执行设置环境,防止出现某些问题。。。
另外这里涉及流程控制:
- 先执行fn(a)
- 再执行finally中的部分
- 最后返回fn(a)的执行结果
graph TD; unbatchedUpdates -->|NoContext| flushSyncCallbackQueue;
- updateContainer
此处调用:updateContainer(children, fiberRoot, parentComponent, callback);
简化后的代码如下:
1 | function updateContainer( |
- requestCurrentTimeForUpdate
简化后的代码如下:
1 | // Expiration times are computed by adding to the current time (the start |
首次加载前两个条件都会越过,直接到新建
- msToExpirationTime
简化后的代码:
1 | // Max 31 bit integer. The max integer size in V8 for 32-bit systems. |
上面的公式中:
|0
表示取整
- now
1 | if ( |
这里涉及到以前没有接触过的东西:
time origin
在浏览器普通文档环境里,time origin的计算如下:
If the script’s global object is a Window, the time origin is determined as follows:
- If the current Document is the first one loaded in the Window, the time origin is the time at which the browser context was created.
- If during the process of unloading the previous document which was loaded in the window, a confirmation dialog was displayed to let the user confirm whether or not to leave the previous page, the time origin is the time at which the user confirmed that navigating to the new page was acceptable.
- If neither of the above determines the time origin, then the time origin is the time at which the navigation responsible for creating the window’s current Document took place.
performance.now()返回的是调用时距离time origin的区间差值,双精度浮点数格式,详见参考4
至此,计算得到currentTime,回到updateContainer
- requestCurrentSuspenseConfig
1 | function requestCurrentSuspenseConfig(): null | SuspenseConfig { |
首次渲染时,返回null,ReactCurrentBatchConfig.suspense的值获取的比较绕,用到SuspenseConfig情况时再说。
- computeExpirationForFiber
1 |
|
- getContextForSubtree
1 | function getContextForSubtree( |
- createUpdate
1 | export const UpdateState = 0; |
update是包含特殊字段的对象。
- enqueueUpdate
1 | function enqueueUpdate<State>(fiber: Fiber, update: Update<State>) { |
这个函数是将一个update放入了updateQueue,fiber是RootFiber。
graph TD; RootFiber -->|.updateQueue.shared| Update; Update -->|.next| Update;
数据结构
这里总结一下updateContainer中,在进入scheduleUpdateOnFiber之前,各个变量的值都是啥
1 | // ==================element========================= |
参考资料
- https://segmentfault.com/q/1010000019803174
- https://juejin.im/post/5dd3bebbe51d453da86c1185#heading-11
- https://developer.mozilla.org/en-US/docs/Web/API/DOMHighResTimeStamp#The_time_origin
- https://developer.mozilla.org/en-US/docs/Web/API/Performance/now
- https://react.jokcy.me/book/update/expiration-time.html