React源码解析系列文章欢迎阅读:
React16源码解析(一)- 图解Fiber架构
React16源码解析(二)-建立更新
React16源码解析(三)-ExpirationTime
React16源码解析(四)-Scheduler
React16源码解析(五)-更新流程渲染阶段1
React16源码解析(六)-更新流程渲染阶段2
React16源码解析(七)-更新流程渲染阶段3
React16源码解析(八)-更新流程提交阶段
正在更新中...node
在React中建立更新主要有下面三种方式:react
一、ReactDOM.render() || hydrate
二、setState
三、forceUpdatesegmentfault
注:
除了上面的还有react 16.8 引进的hooks 中的useState,这个咱们后续再讲。
hydrate是服务端渲染相关的,这块我并不会重点讲解。promise
调用legacyRenderSubtreeIntoContainer()浏览器
const ReactDOM: Object = { // ...... render( element: React$Element<any>,//传入的React组件 container: DOMContainer,//挂载的容器节点 callback: ?Function,//挂载后的回调函数 ) { return legacyRenderSubtreeIntoContainer( null, element, container, false, callback, ); }, // ...... }
一、root = 建立ReactRoot
二、调用root.render()数据结构
function legacyRenderSubtreeIntoContainer( parentComponent: ?React$Component<any, any>,//null children: ReactNodeList,//传入进来须要挂在的class component container: DOMContainer,//根节点 forceHydrate: boolean,//false callback: ?Function,//挂载完成后的回调函数 ) { // ...... // 是否存在根节点 初次渲染是不存在根节点的 let root: Root = (container._reactRootContainer: any); if (!root) { // 一、建立ReactRoot 赋值给container._reactRootContainer和root(这里发生了不少事,一件很重要很重要的事 生成了fiber结构树。。) root = container._reactRootContainer = legacyCreateRootFromDOMContainer( container, forceHydrate, ); // ...... } else { // ...... // 二、调用root.render() root.render(children, callback); } return DOMRenderer.getPublicRootInstance(root._internalRoot); }
一、清除全部子元素
二、建立 new ReactRoot节点架构
function legacyCreateRootFromDOMContainer( container: DOMContainer,//根节点 forceHydrate: boolean,//false ): Root { // 服务端渲染相关 是否合并原先存在的dom节点 通常是false const shouldHydrate = forceHydrate || shouldHydrateDueToLegacyHeuristic(container); // 一、清除全部子元素,经过container.lastchild循环来清除container的全部内容,由于咱们的属于首次渲染,container里边不包含任何元素 if (!shouldHydrate) { let warned = false; let rootSibling; while ((rootSibling = container.lastChild)) { // ...... container.removeChild(rootSibling); } } // Legacy roots are not async by default. const isConcurrent = false; // 二、建立 new ReactRoot节点 return new ReactRoot(container, isConcurrent, shouldHydrate); }
从ReactRoot中, 咱们把createContainer返回值赋给了 实例的_internalRoot, 往下看createContainerapp
function ReactRoot( container: Container, isConcurrent: boolean, hydrate: boolean, ) { // 这里建立了一个FiberRoot const root = DOMRenderer.createContainer(container, isConcurrent, hydrate); this._internalRoot = root; }
从createContainer看出, createContainer其实是直接返回了createFiberRoot, 而createFiberRoot则是经过createHostRootFiber函数的返回值uninitializedFiber,并将其赋值在root对象的current上, 这里须要注意一个点就是,uninitializedFiber的stateNode的值是root, 即他们互相引用。dom
建立一个RootFiber -> createHostRootFiber() -> createFiber() -> new FiberNode()异步
这里建立的这个RootFiber里面的绝大部分属性都是初始值null或者是NoWork。因此具体代码我就没有贴出来了。
这里我提下有意义的点:
RootFiber上的tag会被赋值为 HostRoot。这个以后会用来判断节点类型。
还有这里建立的FiberRoot还有一个containerInfo置为ReactDOM.render第二个参数传入进来的容器节点。这个后续挂载的时候会用到。
export function createContainer( containerInfo: Container, isConcurrent: boolean, hydrate: boolean, ): OpaqueRoot { return createFiberRoot(containerInfo, isConcurrent, hydrate); } export function createFiberRoot( containerInfo: any, isConcurrent: boolean, hydrate: boolean, ): FiberRoot { // 一、建立了一个RootFiber const uninitializedFiber = createHostRootFiber(isConcurrent); // 二、互相引用 // RootFiber.stateNode --> FiberRoot // FiberRoot.current --> RootFiber let root; root = { current: uninitializedFiber, containerInfo: containerInfo, // ...... } uninitializedFiber.stateNode = root; // 三、return了这个FiberRoot return ((root: any): FiberRoot); }
这里牵扯到两种react中的数据结构,第一个FiberRoot,也就是上面createFiberRoot函数返回的对象。
type BaseFiberRootProperties = {| // root节点,render方法接收的第二个参数 containerInfo: any, // 只有在持久更新中会用到,也就是不支持增量更新的平台,react-dom不会用到 pendingChildren: any, // 当前应用对应的Fiber对象,是Root Fiber current: Fiber, // 一下的优先级是用来区分 // 1) 没有提交(committed)的任务 // 2) 没有提交的挂起任务 // 3) 没有提交的可能被挂起的任务 // 咱们选择不追踪每一个单独的阻塞登记,为了兼顾性能 // The earliest and latest priority levels that are suspended from committing. // 最老和新的在提交的时候被挂起的任务 earliestSuspendedTime: ExpirationTime, latestSuspendedTime: ExpirationTime, // The earliest and latest priority levels that are not known to be suspended. // 最老和最新的不肯定是否会挂起的优先级(全部任务进来一开始都是这个状态) earliestPendingTime: ExpirationTime, latestPendingTime: ExpirationTime, // The latest priority level that was pinged by a resolved promise and can // be retried. // 最新的经过一个promise被reslove而且能够从新尝试的优先级 latestPingedTime: ExpirationTime, // 若是有错误被抛出而且没有更多的更新存在,咱们尝试在处理错误前同步从新从头渲染 // 在`renderRoot`出现没法处理的错误时会被设置为`true` didError: boolean, // 正在等待提交的任务的`expirationTime` pendingCommitExpirationTime: ExpirationTime, // 已经完成的任务的FiberRoot对象,若是你只有一个Root,那他永远只多是这个Root对应的Fiber,或者是null // 在commit阶段只会处理这个值对应的任务 finishedWork: Fiber | null, // 在任务被挂起的时候经过setTimeout设置的返回内容,用来下一次若是有新的任务挂起时清理还没触发的timeout timeoutHandle: TimeoutHandle | NoTimeout, // 顶层context对象,只有主动调用`renderSubtreeIntoContainer`时才会有用 context: Object | null, pendingContext: Object | null, // 用来肯定第一次渲染的时候是否须要融合 +hydrate: boolean, // 当前root上剩余的过时时间 // TODO: 提到renderer里面区处理 nextExpirationTimeToWorkOn: ExpirationTime, // 当前更新对应的过时时间 expirationTime: ExpirationTime, // List of top-level batches. This list indicates whether a commit should be // deferred. Also contains completion callbacks. // TODO: Lift this into the renderer // 顶层批次(批处理任务?)这个变量指明一个commit是否应该被推迟 // 同时包括完成以后的回调 // 貌似用在测试的时候? firstBatch: Batch | null, // root之间关联的链表结构 nextScheduledRoot: FiberRoot | null, |};
这里就是createHostRootFiber函数返回的fiber对象。注意这里其实每个节点都对应一个fiber对象,不是Root专有的哦。
// Fiber对应一个组件须要被处理或者已经处理了,一个组件能够有一个或者多个Fiber type Fiber = {| // 标记不一样的组件类型 // export const FunctionComponent = 0; // export const ClassComponent = 1; // export const IndeterminateComponent = 2; // Before we know whether it is function or class // export const HostRoot = 3; // Root of a host tree. Could be nested inside another node. // export const HostPortal = 4; // A subtree. Could be an entry point to a different renderer. // export const HostComponent = 5; // export const HostText = 6; // export const Fragment = 7; // export const Mode = 8; // export const ContextConsumer = 9; // export const ContextProvider = 10; // export const ForwardRef = 11; // export const Profiler = 12; // export const SuspenseComponent = 13; // export const MemoComponent = 14; // export const SimpleMemoComponent = 15; // export const LazyComponent = 16; // export const IncompleteClassComponent = 17; tag: WorkTag, // ReactElement里面的key key: null | string, // ReactElement.type,标签类型,也就是咱们调用`createElement`的第一个参数 elementType: any, // The resolved function/class/ associated with this fiber. // 异步组件resolved以后返回的内容,通常是`function`或者`class` type: any, // The local state associated with this fiber. // 跟当前Fiber相关本地状态(好比浏览器环境就是DOM节点) stateNode: any, // 指向他在Fiber节点树中的`parent`,用来在处理完这个节点以后向上返回 return: Fiber | null, // 单链表树结构 // 指向本身的第一个子节点 child: Fiber | null, // 指向本身的兄弟结构 // 兄弟节点的return指向同一个父节点 sibling: Fiber | null, index: number, // ref属性 ref: null | (((handle: mixed) => void) & {_stringRef: ?string}) | RefObject, // 新的变更带来的新的props pendingProps: any, // 上一次渲染完成以后的props memoizedProps: any, // 该Fiber对应的组件产生的Update会存放在这个队列里面 updateQueue: UpdateQueue<any> | null, // 上一次渲染的时候的state memoizedState: any, // 一个列表,存放这个Fiber依赖的context firstContextDependency: ContextDependency<mixed> | null, // 用来描述当前Fiber和他子树的`Bitfield` // 共存的模式表示这个子树是否默认是异步渲染的 // Fiber被建立的时候他会继承父Fiber // 其余的标识也能够在建立的时候被设置 // 可是在建立以后不该该再被修改,特别是他的子Fiber建立以前 mode: TypeOfMode, // Effect // 用来记录Side Effect // Don't change these two values. They're used by React Dev Tools. // export const NoEffect = /* */ 0b00000000000; // export const PerformedWork = /* */ 0b00000000001; // You can change the rest (and add more). // export const Placement = /* */ 0b00000000010; // export const Update = /* */ 0b00000000100; // export const PlacementAndUpdate = /* */ 0b00000000110; // export const Deletion = /* */ 0b00000001000; // export const ContentReset = /* */ 0b00000010000; // export const Callback = /* */ 0b00000100000; // export const DidCapture = /* */ 0b00001000000; // export const Ref = /* */ 0b00010000000; // export const Snapshot = /* */ 0b00100000000; // Update & Callback & Ref & Snapshot // export const LifecycleEffectMask = /* */ 0b00110100100; // Union of all host effects // export const HostEffectMask = /* */ 0b00111111111; // export const Incomplete = /* */ 0b01000000000; // export const ShouldCapture = /* */ 0b10000000000; effectTag: SideEffectTag, // 单链表用来快速查找下一个side effect nextEffect: Fiber | null, // 子树中第一个side effect firstEffect: Fiber | null, // 子树中最后一个side effect lastEffect: Fiber | null, // 表明任务在将来的哪一个时间点应该被完成 // 不包括他的子树产生的任务 expirationTime: ExpirationTime, // 快速肯定子树中是否有不在等待的变化 childExpirationTime: ExpirationTime, // 在Fiber树更新的过程当中,每一个Fiber都会有一个跟其对应的Fiber // 咱们称他为`current <==> workInProgress` // 在渲染完成以后他们会交换位置 alternate: Fiber | null, // 下面是调试相关的,收集每一个Fiber和子树渲染时间的 actualDuration?: number, // If the Fiber is currently active in the "render" phase, // This marks the time at which the work began. // This field is only set when the enableProfilerTimer flag is enabled. actualStartTime?: number, // Duration of the most recent render time for this Fiber. // This value is not updated when we bailout for memoization purposes. // This field is only set when the enableProfilerTimer flag is enabled. selfBaseDuration?: number, // Sum of base times for all descedents of this Fiber. // This value bubbles up during the "complete" phase. // This field is only set when the enableProfilerTimer flag is enabled. treeBaseDuration?: number, // Conceptual aliases // workInProgress : Fiber -> alternate The alternate used for reuse happens // to be the same as work in progress. // __DEV__ only _debugID?: number, _debugSource?: Source | null, _debugOwner?: Fiber | null, _debugIsCurrentlyTiming?: boolean, |};
通过上面的步骤,建立好了ReactRoot。初始化完成了。下面开始root.render。
咱们回到legacyRenderSubtreeIntoContainer函数,前面一堆讲解的是调用legacyCreateRootFromDOMContainer方法咱们获得了一个ReactRoot对象。reactRoot的原型上面咱们找到了render方法:
ReactRoot.prototype.render = function( children: ReactNodeList, callback: ?() => mixed, ): Work { // 这个就是咱们上面建立的FiberRoot对象 const root = this._internalRoot; // ...... DOMRenderer.updateContainer(children, root, null, work._onCommit); return work; };
这个函数里面使用了 currentTime 和 expirationTime, currentTime是用来计算expirationTime的,expirationTime表明着优先级, 这个留在后续分析。后续紧接着调用了updateContainerAtExpirationTime。
export function updateContainer( element: ReactNodeList, container: OpaqueRoot, parentComponent: ?React$Component<any, any>, callback: ?Function, ): ExpirationTime { // 这个current就是FiberRoot对应的RootFiber const current = container.current; const currentTime = requestCurrentTime(); const expirationTime = computeExpirationForFiber(currentTime, current); return updateContainerAtExpirationTime( element, container, parentComponent, expirationTime, callback, ); }
注:这个函数在ReactFiberReconciler.js里面。
这里将current(即Fiber实例)提取出来, 并做为参数传入调用scheduleRootUpdate
export function updateContainerAtExpirationTime( element: ReactNodeList, container: OpaqueRoot, parentComponent: ?React$Component<any, any>, expirationTime: ExpirationTime, callback: ?Function, ) { const current = container.current; // ...... return scheduleRootUpdate(current, element, expirationTime, callback); }
这个函数主要执行了两个操做:
一、建立更新createUpdate并放到更新队列enqueueUpdate,建立更新的具体细节稍后再讲哈。由于待会咱们会发现其余地方也用到了。
二、个是执行sheculeWork函数,进入React异步渲染的核心:React Scheduler,这个我后续文章详细讲解。
function scheduleRootUpdate( current: Fiber, element: ReactNodeList, expirationTime: ExpirationTime, callback: ?Function, ) { // ...... // 一、建立一个update对象 const update = createUpdate(expirationTime); update.payload = {element}; // ...... // 二、将刚建立的update对象入队到fiber.updateQueue队列中 enqueueUpdate(current, update); // 三、开始进入React异步渲染的核心:React Scheduler scheduleWork(current, expirationTime); return expirationTime; }
以上的过程我画了张图:
虽然我尚未讲解到class component 的渲染过程,可是这个不影响我如今要讨论的内容~
以下咱们调用this.setState方法的时候,调用了this.updater.enqueueSetState
Component.prototype.setState = function(partialState, callback) { this.updater.enqueueSetState(this, partialState, callback, 'setState'); };
先无论this.updater何时被赋值的,直接看到ReactFiberClassComponent.js中的enqueueSetState,这就是咱们调用setState执行的enqueueSetState方法。
const classComponentUpdater = { // ...... enqueueSetState(inst, payload, callback) { // inst 就是咱们调用this.setState的this,也就是classComponent实例 // 获取到当前实例上的fiber const fiber = ReactInstanceMap.get(inst); const currentTime = requestCurrentTime(); // 计算当前fiber的到期时间(优先级) const expirationTime = computeExpirationForFiber(currentTime, fiber); // 建立更新一个更新update const update = createUpdate(expirationTime); //payload是setState传进来的要更新的对象 update.payload = payload; //callback就是setState({},()=>{})的回调函数 if (callback !== undefined && callback !== null) { if (__DEV__) { warnOnInvalidCallback(callback, 'setState'); } update.callback = callback; } // 把更新放到队列UpdateQueue enqueueUpdate(fiber, update); // 开始进入React异步渲染的核心:React Scheduler scheduleWork(fiber, expirationTime); }, // ...... }
看到上面的代码,是否是发现和上面ReactDOM.render中scheduleRootUpdate很是的类似。其实他们就是同一个更新原理呢~
废话很少说,先上代码。也是在ReactFiberClassComponent.js中classComponentUpdater对象中。
const classComponentUpdater = { // ...... enqueueForceUpdate(inst, callback) { const fiber = ReactInstanceMap.get(inst); const currentTime = requestCurrentTime(); const expirationTime = computeExpirationForFiber(currentTime, fiber); const update = createUpdate(expirationTime); //与setState不一样的地方 //默认是0更新,须要改为2强制更新 update.tag = ForceUpdate; if (callback !== undefined && callback !== null) { if (__DEV__) { warnOnInvalidCallback(callback, 'forceUpdate'); } update.callback = callback; } enqueueUpdate(fiber, update); scheduleWork(fiber, expirationTime); }, // ...... }
看到代码的咱们很开心,简直就是enqueueSetState的孪生兄弟。我就不详说啦。
到这里咱们总结一下上面三种更新的流程:
(1)获取节点对应的fiber对象
(2)计算currentTime
(3)根据(1)fiber和(2)currentTime计算fiber对象的expirationTime
(4)根据(3)expirationTime建立update对象
(5)将setState中要更新的对象赋值到(4)update.payload,ReactDOM.render是{element}
(6)将callback赋值到(4)update.callback
(7)update入队updateQueue
(8)进行任务调度
上面三种建立更新的方式中都建立了一个叫update的对象。那这个对象里面究竟是什么呢?充满好奇的咱们点开createUpdate函数瞧瞧:
export function createUpdate(expirationTime: ExpirationTime): Update<*> { return { // 过时时间 expirationTime: expirationTime, // export const UpdateState = 0; // export const ReplaceState = 1; // export const ForceUpdate = 2; // export const CaptureUpdate = 3; // 指定更新的类型,值为以上几种 // 提下CaptureUpdate,在React16后有一个ErrorBoundaries功能 // 即在渲染过程当中报错了,能够选择新的渲染状态(提示有错误的状态),来更新页面 // 0更新 1替换 2强制更新 3捕获性的更新 tag: UpdateState, // 更新内容,好比`setState`接收的第一个参数 // 第一次渲染ReactDOM.render接收的是payload = {element}; payload: null, // 更新完成后对应的回调,`setState`,`render`都有 callback: null, // 指向下一个更新 next: null, // 指向下一个`side effect`,这块内容后续讲解 nextEffect: null, }; }
就是返回了个简单的对象。对象每一个属性的解释我都写在上面了。
UpdateQueue是一个单向链表,用来存放update。每一个update用next链接。它的结构以下:
//建立更新队列 export function createUpdateQueue<State>(baseState: State): UpdateQueue<State> { const queue: UpdateQueue<State> = { // 应用更新后的state // 每次的更新都是在这个baseState基础上进行更新 baseState, // 队列中的第一个update firstUpdate: null, // 队列中的最后一个update lastUpdate: null, // 队列中第一个捕获类型的update firstCapturedUpdate: null, // 队列中最后一个捕获类型的update lastCapturedUpdate: null, // 第一个side effect firstEffect: null, // 最后一个side effect lastEffect: null, // 第一个和最后一个捕获产生的`side effect` firstCapturedEffect: null, lastCapturedEffect: null, }; return queue; }
建立了update对象以后,紧接着调用了enqueueUpdate,把update对象放到队列enqueueUpdate。同时保证current和workInProgress的updateQueue是一致的,即fiber.updateQueue和fiber.alternate.updateQueue保持一致。
export function enqueueUpdate<State>(fiber: Fiber, update: Update<State>) { // 保证current和workInProgress的updateQueue是一致的 // alternate即workInProgress const alternate = fiber.alternate; // current的队列 let queue1; // alternate的队列 let queue2; // 若是alternate为空 if (alternate === null) { // There's only one fiber. queue1 = fiber.updateQueue; queue2 = null; // 若是queue1仍为空,则初始化更新队列 if (queue1 === null) { queue1 = fiber.updateQueue = createUpdateQueue(fiber.memoizedState); } } else { // 若是alternate不为空,则取各自的更新队列 queue1 = fiber.updateQueue; queue2 = alternate.updateQueue; if (queue1 === null) { if (queue2 === null) { // 初始化 queue1 = fiber.updateQueue = createUpdateQueue(fiber.memoizedState); queue2 = alternate.updateQueue = createUpdateQueue( alternate.memoizedState, ); } else { // 若是queue2存在但queue1不存在的话,则根据queue2复制queue1 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) { // 将update放入queue1中 appendUpdateToQueue(queue1, update); } else { // 两个队列共享的是用一个update // 若是两个都是空队列,则添加update if (queue1.lastUpdate === null || queue2.lastUpdate === null) { appendUpdateToQueue(queue1, update); appendUpdateToQueue(queue2, update); } else { // 若是两个都不是空队列,因为两个结构共享,因此只在queue1加入update // 在queue2中,将lastUpdate指向update appendUpdateToQueue(queue1, update); queue2.lastUpdate = update; } }
总结上面过程:
(1)queue1取的是fiber.updateQueue;
queue2取的是alternate.updateQueue
(2)若是二者均为null,则调用createUpdateQueue()获取初始队列
(3)若是二者之一为null,则调用cloneUpdateQueue()从对方中获取队列
(4)若是二者均不为null,则将update做为lastUpdate
注:两个队列共享的是同一个update。
上面三种更新最后都调用了scheduleWork(fiber, expirationTime)进入React异步渲染的核心:React Scheduler。后续文章详细讲解。
文章若有不妥,欢迎指正~