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。後續文章詳細講解。
文章若有不妥,歡迎指正~