更新流程
基于 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.tagsetState -> 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.updateQueuequeue2是应用程序上一次更新时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 的数据