更新流程
基于 React v16.8.4
源码在浏览器进行调试
调试方法借鉴: 我是如何阅读源码的
#
总体流程概述#
首次渲染检查容器是否合法,构建
fiberRoot
对象,fiberRoot
对象是整个Fiber
架构的根节点对象创建更新对象(更新内容为应用程序的根组件)并将更新加入到更新队列
React
向任务调度器申请立即执行,任务调度器安排该任务立即执行 (获取到执行权则进入render
阶段进入
render
阶段,此时主要工作为构建workInProgress
树构建过程中会做一些重要的工作,如为结点标记
effectTag
、对结点进行diff
处理,收集Effect List
调用生命周期函数等收集好
Effect List
后会进入commit
阶段,此阶段主要是将Effect List
更新到屏幕,然后渲染结束
#
更新渲染不再重复构建
fiberRoot
对象,在更新阶段已经存在于系统内存之中此时的更新内容一般为组件内部发生变化的
state props
在进入
render
阶段前要进行任务调度 只有申请到更新执行权或者任务到期才能进行后续渲染工作此时构建
wIP
树会尽量复用上一次创建的Fiber
结点 同时对需要更新的结点标记对应的effectTag
在
commit
阶段 得到的Effect List
是被标记了effect tag
的Fiber
结点集合(一个链表) 一般是wIP
树的子集
#
创建更新ReactDOM.render()
this.setState()
this.forceUpdate()
hydrate
是服务端渲染相关useState()
RecatDOM.render
#
- 检验
container
是否有效 - 初始化
fiberRoot
对象 - 为应用程序的首次渲染创建更新对象
update
ReactDOM.render(element, document.getElementById('root'));
const ReactDOM = { render: function (element, container, callback) { // 会先检验container是否有效,无效则停止执行且抛出错误 return legacyRenderSubtreeIntoContainer(null, element, container, false, callback); }}
legacyRenderSubtreeIntoContainer
- 调用
legacyCreateRootFromDOMContainer
创建ReactRoot
对象 - 调用
root.render()
进行更新
// 未分析forceHydrate、callbackfunction legacyRenderSubtreeIntoContainer(parentComponent, children, container, forceHydrate, callback) {
// 是否存在根节点 初次渲染是不存在根节点的 var root = container._reactRootContainer; if (!root) { // Initial mount root = container._reactRootContainer = legacyCreateRootFromDOMContainer(container, forceHydrate);
// Initial mount should not be batched. unbatchedUpdates(function () { if (parentComponent != null) { root.legacy_renderSubtreeIntoContainer(parentComponent, children, callback); } else { root.render(children, callback); } }); } else { // Update if (parentComponent != null) { root.legacy_renderSubtreeIntoContainer(parentComponent, children, callback); } else { root.render(children, callback); } } // 还是返回了创建的FiberRoot对象? return getPublicRootInstance(root._internalRoot);}
legacyCreateRootFromDOMContainer
ReactRoot
function legacyCreateRootFromDOMContainer(container, forceHydrate) {
// Legacy roots are not async by default. var isConcurrent = false; return new ReactRoot(container, isConcurrent, shouldHydrate);}
// ReactRoot构造函数function ReactRoot(container, isConcurrent, hydrate) { // 创建一个FiberRoot 并赋值给 实例的 _internalRoot var root = createContainer(container, isConcurrent, hydrate); this._internalRoot = root;}
createContainer
createFiberRoot
createContainer -> createFiberRoot -> createHostRootFiber -> createFiber -> new FiberNode()
function createContainer(containerInfo, isConcurrent, hydrate) { return createFiberRoot(containerInfo, isConcurrent, hydrate);}
function createFiberRoot(containerInfo, isConcurrent, hydrate) { // Cyclic construction. This cheats the type system right now because stateNode is any. // 创建一个RootFiber var uninitializedFiber = createHostRootFiber(isConcurrent); // 互相引用 形成闭环
// FiberRoot.current -> RootFIber // 指向了 Fiber 树的第一个 Fiber 结点 // RootFiber.stateNode -> FiberRoot var root = void 0; root = { current: uninitializedFiber, containerInfo: containerInfo, // ...... } uninitializedFiber.stateNode = root;
return root: FiberRoot;}
function createHostRootFiber(isConcurrent) { var mode = isConcurrent ? ConcurrentMode | StrictMode : NoContext; if (enableProfilerTimer && isDevToolsPresent) { mode |= ProfileMode; } return createFiber(HostRoot, null, null, mode);}
var createFiber = function (tag, pendingProps, key, mode) { return new FiberNode(tag, pendingProps, key, mode);};
开始调用root.render()
function unbatchedUpdates(fn, a) { if (isBatchingUpdates && !isUnbatchingUpdates) { isUnbatchingUpdates = true; try { return fn(a); } finally { isUnbatchingUpdates = false; } } return fn(a);}
// 原型链上添加 实例root直接调用ReactRoot.prototype.render = function (children, callback) { var root = this._internalRoot; var work = new ReactWork(); callback = callback === undefined ? null : callback;
if (callback !== null) { work.then(callback); } updateContainer(children, root, null, work._onCommit); return work;};
// 在这里计算了 expirationTimefunction updateContainer(element, container, parentComponent, callback) { // 这个current就是FiberRoot对应的RootFiber??绕口 var current$$1 = container.current; var currentTime = requestCurrentTime(); var expirationTime = computeExpirationForFiber(currentTime, current$$1); return updateContainerAtExpirationTime(element, container, parentComponent, expirationTime, callback);} // 将current(即Fiber实例)提取出来,并作为参数传入调用scheduleRootUpdatefunction updateContainerAtExpirationTime(element, container, parentComponent, expirationTime, callback) { // TODO: If this is a nested container, this won't be the root. var current$$1 = container.current; // ... return scheduleRootUpdate(current$$1, element, expirationTime, callback);}
scheduleRootUpdate
// keyfunction scheduleRootUpdate(current$$1, element, expirationTime, callback) {
var update = createUpdate(expirationTime); // Caution: React DevTools currently depends on this property // being called "element". update.payload = { element: element };
callback = callback === undefined ? null : callback; if (callback !== null) { update.callback = callback; }
flushPassiveEffects(); enqueueUpdate(current$$1, update); scheduleWork(current$$1, expirationTime);
return expirationTime;}
setState
#
setState
和forceUpdate
的代码几乎一模一样,唯一的区别是Update.tag
setState -> enqueueSetState -> enqueueUpdate
当我们使用setState(...)
时,React
会创建一个更新update
对象,然后通过调用enqueueUpdate
函数将其加入到更新队列updateQueue
Component.prototype.setState = function (partialState, callback) { this.updater.enqueueSetState(this, partialState, callback, 'setState');};
enqueueSetState enqueueForceUpdate
var classComponentUpdater = { enqueueSetState: function (inst, payload, callback) { // inst 当前实例 获取到当前实例上的fiber var fiber = get(inst); var currentTime = requestCurrentTime(); // 计算当前fiber的到期时间(优先级) var expirationTime = computeExpirationForFiber(currentTime, fiber); // 创建更新一个更新update var update = createUpdate(expirationTime); // payload是setState传进来的要更新的对象 update.payload = payload; // callback就是setState({},()=>{})的回调函数 if (callback !== undefined && callback !== null) { update.callback = callback; }
flushPassiveEffects(); // 把更新放到队列UpdateQueue enqueueUpdate(fiber, update); // 开始进入React异步渲染的核心:React Scheduler scheduleWork(fiber, expirationTime); }, enqueueForceUpdate: function (inst, callback) { var fiber = get(inst); var currentTime = requestCurrentTime(); var expirationTime = computeExpirationForFiber(currentTime, fiber);
var update = createUpdate(expirationTime); update.tag = ForceUpdate;
if (callback !== undefined && callback !== null) { update.callback = callback; }
flushPassiveEffects(); enqueueUpdate(fiber, update); scheduleWork(fiber, expirationTime); }}
总结三种更新流程
- 获取节点对应的
fiber
对象 - 计算
currentTime
- 根据
fiber currentTime
计算出fiber
对象的expirationTime
- 根据
expirationTime
创建update
对象 - 将
setState
中要更新的对象赋值到update.payload
- 如果有
callback
将其赋值到update.callback = callback
- 将
update
对象加入到updateQueue
- 进行任务调度
#
加入队列我们可以发现不管是ReactDOM.render() -> scheduleRootUpdate
还是 this.setState() -> enqueueSetState
创建更新后的流程都是
- 调用
createUpdate
创建一个更新对象update
- 调用
enqueueUpdate
将update
对象加入到updateQueue
- 调用
scheduleWork
进入异步渲染到核心:React Scheduler
enqueueUpdate
#
- 保证
current
树 和wIP
树的updateQueue
是一致的
function enqueueUpdate(fiber, update) { // Update queues are created lazily. var alternate = fiber.alternate; var queue1 = void 0; // current的队列 var queue2 = void 0; // alternate的队列 if (alternate === null) { // There's only one fiber. queue1 = fiber.updateQueue; queue2 = null; // alternate & current 都为空 则初始化更新队列 if (queue1 === null) { queue1 = fiber.updateQueue = createUpdateQueue(fiber.memoizedState); } } else { // There are two owners. queue1 = fiber.updateQueue; queue2 = alternate.updateQueue; if (queue1 === null) { if (queue2 === null) { // Neither fiber has an update queue. Create new ones. queue1 = fiber.updateQueue = createUpdateQueue(fiber.memoizedState); queue2 = alternate.updateQueue = createUpdateQueue(alternate.memoizedState); } else { // Only one fiber has an update queue. Clone to create a new one. queue1 = fiber.updateQueue = cloneUpdateQueue(queue2); } } else { if (queue2 === null) { // Only one fiber has an update queue. Clone to create a new one. queue2 = alternate.updateQueue = cloneUpdateQueue(queue1); } else { // Both owners have an update queue. } } } if (queue2 === null || queue1 === queue2) { // There's only a single queue. appendUpdateToQueue(queue1, update); } else { // There are two queues. We need to append the update to both queues, // while accounting for the persistent structure of the list — we don't // want the same update to be added multiple times. if (queue1.lastUpdate === null || queue2.lastUpdate === null) { // One of the queues is not empty. We must add the update to both queues. appendUpdateToQueue(queue1, update); appendUpdateToQueue(queue2, update); } else { // Both queues are non-empty. The last update is the same in both lists, because of structural sharing. So, only append to one of the lists. appendUpdateToQueue(queue1, update); // But we still need to update the `lastUpdate` pointer of queue2. queue2.lastUpdate = update; } }}
queue1
是应用程序运行过程中current
树上当前Fiber
结点最新队列fiber.updateQueue
queue2
是应用程序上一次更新时workInProgress
树Fiber
结点的更新队列fiber.alternate.updateQueue
- 如果两者均为
null
,则调用createUpdateQueue()
获取初始队列 - 如果两者之一为
null
,则调用cloneUpdateQueue()
从对方中获取队列 - 如果两者均不为
null
,则将update
作为queue2
的lastUpdate
整个更新队列对象通过 firstUpdate
属性和更新对象的 next
属性层层引用形成了链表结构。同时更新队列对象中也可以通过 lastUpdate
属性直接连接到最后一个更新对象
processUpdateQueue
#
React
在 render
阶段会处理更新队列,会调用processUpdateQueue
函数
processUpdateQueue
函数用于处理更新队列,在该函数内部使用循环的方式来遍历队列,通过 update.next
依次取出更新对象进行合并,合并更新对象的方式是:
- 如果
setState
传入的参数类型是function
,则通过payload2.call(instance, prevState, nextProps)
获取更新对象; - 如果
setState
传入的参数类型是object
,则可直接获取更新对象 - 最后通过使用
Object.assign()
合并两个更新对象并返回,如果属性相同的情况下则取最后一次值
在处理更新队列时,React
会根据更新对象中携带的 state
相同属性进行合并,保留队列中最后一次属性值,以此作为前后结点 diff
的数据