使用react也有將近一年了,在使用的過程當中,我相信你也會像我同樣,存在不少疑惑的點; 舉些例子,好比爲何react有些生命鉤子會執行屢次,而有些只會安全的執行一次?react 16 大版本更新的fiber究竟是個什麼東西?諸如此類的問題,我也百思不得其解;因此我踏上了探究源碼之路;html
總所周知,react源碼不是通常的多,直接閱讀react源碼,真的是勸退... 在蒐集react源碼資料的時候,發現比較全和新的資料也不多,偶然一次機會看到奇舞團大佬按照react源碼思路本身debug造了一個;在學習他的源碼時候,我也私下和他交流了不少,真的是聽君一席話,勝讀十年書呀;2333....(仍是本身太菜了,很是感謝大佬解答個人問題)vue
言歸正傳,這裏強烈推薦他電子書,源碼系列文章是基於最新的16.13.1解析的; 雖然沒有更完,可是寫得至關精彩,反正我是看了還想看那種。(有點崔更了,哈哈)react
本文是根據最新的16.13.1進行解析,目的是把總體的源碼流程看懂個大概,並不會深刻到很細節的東西; 也就是說把react的總體更新流程弄明白,能夠幫助你更好的去探究最終的源碼細節,若是有不正確的地方,還望大佬們指正; 雖然說如今更新到了16.13.1版本了,可是總體的架構依然沒有變,這裏我推薦幾個必讀的資料,很精彩;git
在瞭解react架構以前,咱們還須要瞭解一下瀏覽器渲染原理,主流的瀏覽器刷新頻率爲60Hz,即每(1000ms / 60Hz)16.6ms瀏覽器刷新一次。咱們知道,JS是能夠操做DOM的,因此JS腳本執行和瀏覽器佈局、繪製是處於同一線程(渲染線程)。 也就是瀏覽器在一幀的時間內要完成如下工做github
在16大版本以前,也就是React15架構只分爲兩層,Reconciler(協調器,可不中斷)+ Renderer(渲染器,不可中斷);也就是說協調階段,同步(遞歸更新完)更新的;這很容易形成JS執行時間過長,超出了16.6ms,也就是說一旦開始更新,就不可中斷,一口氣作完。會形成卡頓,這樣的用戶體驗很是差;算法
react團隊發現,讓用戶操做感受不到卡頓,操做之外的有延遲,卡頓一下,用戶是徹底能夠接受的;JS執行時間過長,因此react更改了架構;React16架構能夠分爲三層:數組
要理解react的更新流程,我以爲最好的方式是畫流程圖,結合一點源碼註釋;否則在學習源碼的過程會很是的混亂;先看react的初始化階段。瀏覽器
// ReactDOM.render(<App name="Hello"/>, document.querySelector('#app')); const ReactDOM = { render(element, container) { // 建立 FiberRoot const root = container._reactRootContainer = new ReactRoot(container); // 首次渲染不須要批量更新 DOMRenderer.unbatchedUpdates(() => { //調用 FiberRoot 的render方法開始渲染 root.render(element); }) } 複製代碼
export default class ReactRoot { constructor(container) { // RootFiber tag === 3 this.current = new FiberNode(3, null, null); // 初始化rootFiber的updateQueue initializeUpdateQueue(this.current); // RootFiber指向FiberRoot this.current.stateNode = this; // 應用掛載的根DOM節點 this.containerInfo = container; // root下已經render完畢的fiber this.finishedWork = null; } } 複製代碼
batchedUpdates
, 會輸出1,2,3batchedUpdates
,至關於使用定時器的效果,會輸出0,0,0batchedUpdates
handleClick = () => { // 主動`unbatchedUpdates` // setTimeout(() => { // this.countNumber() // }, 0) // setTimeout中沒有`batchedUpdates` setTimeout(() => { batchedUpdates(() => this.countNumber()) }, 0) // 事件處理函數自帶`batchedUpdates`,至關於上面的狀況 // this.countNumber() } countNumber() { const num = this.state.number this.setState({ number: num + 1, }) console.log(this.state.number) this.setState({ number: num + 2, }) console.log(this.state.number) this.setState({ number: num + 3, }) console.log(this.state.number) } 複製代碼
export default class ReactRoot { constructor(container) { // TODO... } render(element) { // RootFiber const current = this.current; // 申請當前的建立更新時間 const currentTime = DOMRenderer.requestCurrentTimeForUpdate(); // expirationTime 過時時間,能夠表明着本次更新任務的優先級; // 不一樣事件觸發的update會產生不一樣priority // 不一樣priority使fiber得到不一樣的expirationTime const expirationTime = DOMRenderer.computeExpirationForFiber(currentTime, current); // 建立更新 const update = createUpdate(expirationTime); // fiber.tag爲HostRoot類型,payload爲對應要渲染的ReactComponents(APP 組件) update.payload = {element}; enqueueUpdate(current, update); // 首次渲染會走這裏,再次更新就直接建立更新對象而後開始調度 return DOMRenderer.scheduleUpdateOnFiber(current, expirationTime); } } 複製代碼
老樣子,咱們仍是直接先上流程圖,根據流程再來看代碼和註釋;在閱讀react源碼的時候,是至關枯燥的,咱們須要一點耐心慢慢解刨; 安全
// 從當前fiber遞歸上去到root,再從root開始work更新 export function scheduleUpdateOnFiber(fiber, expirationTime) { // 注意是值越大,權限越大,和16.7相反了; // 向上冒泡更新,同時更新的過時時間(expirationTime)和子節點的過時時間 (childExpirationTime) // 這樣作的緣由是讓整個fiber樹上更新的最高優先級冒泡到root節點,進行更新 const root = markUpdateTimeFromFiberToRoot(fiber, expirationTime); // root == FiberRoot if (!root) { return; } // 開始安排調度安排調度 ensureRootIsScheduled(root); } 複製代碼
function ensureRootIsScheduled(root) { // 這個變量記錄過時未執行的fiber的expirationTime const lastExpiredTime = root.lastExpiredTime; if (lastExpiredTime !== NoWork) { // ....TODO } // 尋找root(FiberRoot)本次更新的過時時間 const expirationTime = getNextRootExpirationTimeToWorkOn(root); const existingCallbackNode = root.callbackNode; // 本次更新的過時時間實際上是沒有任務 if (expirationTime === NoWork) { // 又存在當前正在進行的異步任務,同步執行掉 if (existingCallbackNode) { root.callbackNode = null; root.callbackExpirationTime = NoWork; root.callbackPriority = Scheduler.NoPriority; } return; } // 從當前時間和expirationTime推斷任務優先級 const currentTime = requestCurrentTimeForUpdate(); const priorityLevel = inferPriorityFromExpirationTime(currentTime, expirationTime); if (existingCallbackNode) { // 該root上已存在schedule的root const existingCallbackNodePriority = root.callbackPriority; const existingCallbackExpirationTime = root.callbackExpirationTime; if (existingCallbackExpirationTime === expirationTime && existingCallbackNodePriority >= priorityLevel) { // 該root已經存在的任務expirationTime和新udpate產生的expirationTime一致 // 這表明他們多是同一個事件觸發產生的update // 且已經存在的任務優先級更高,則能夠取消此次update的render return; } // 不然表明新udpate產生的優先級更高,取消以前的schedule,從新開始一次新的 Scheduler.cancelCallback(existingCallbackNode); } root.callbackExpirationTime = expirationTime; root.callbackPriority = priorityLevel; // 保存Scheduler保存的當前正在進行的異步任務 let callbackNode; // 過時任何和同步任務同樣,不中斷,一口氣更新完; if (expirationTime === Sync) { callbackNode = scheduleSyncCallback(performSyncWorkOnRoot.bind(null, root)); } else { // 正常的異步任務和Concurrent首次渲染走走這裏 callbackNode = Scheduler.scheduleCallback( priorityLevel, performConcurrentWorkOnRoot.bind(null, root), // 根據expirationTime,爲任務計算一個timeout // timeout會影響任務執行優先級 {timeout: expirationTimeToMs(expirationTime) - Scheduler.now()} ) } root.callbackNode = callbackNode; } 複製代碼
function performSyncWorkOnRoot(root) { const lastExpiredTime = root.lastExpiredTime; const expirationTime = lastExpiredTime !== NoWork ? lastExpiredTime : Sync; //先暫時忽略這個函數 flushPassiveEffects(); if (root !== workInProgressRoot || expirationTime !== renderExpirationTime) { // 建立WIP樹進行建立更新,若是WIP樹還存在,說明須要打斷這個任務 prepareFreshStack(root, expirationTime); } //根據WIP樹進行更新 if (workInProgress) { const prevExecutionContext = executionContext; executionContext |= RenderContext; do { // 進入同步的workLoop渲染大循環 workLoopSync(); break; } while (true) // render階段結束,進入commit階段,commit階段不可中斷 commitRoot(root); // 從新安排調度, 以避免又執行不到過時了的任務; ensureRootIsScheduled(root); } return null; } 複製代碼
function workLoopSync() { while (workInProgress) { workInProgress = performUnitOfWork(workInProgress); } } 複製代碼
function performUnitOfWork(unitOfWork) { const current = unitOfWork.alternate; // beginWork會返回fiber.child,不存在next意味着深度優先遍歷已經遍歷到某個子樹的最深層葉子節點 // beginWork 爲render階段的主要工做之一,主要作了以下事: // 根據update更新 state // 根據update更新 props // 根據update更新 effectTag let next = beginWork(current, unitOfWork, renderExpirationTime); // beginWork完成 fiber的diff,能夠更新momoizedProps unitOfWork.memoizedProps = unitOfWork.pendingProps; if (!next) { // completeUnitOfWork 主要作了以下事: // 1.爲 beginWork階段生成的fiber生成對應DOM,併產生DOM樹 // let next = completeWork(current, workInProgress); // 2. 將child fiber的expirationTime冒泡到父級 // 這樣在父級就能直到子孫中優先級最高到expirationTime // resetChildExpirationTime(workInProgress); // 3. 組裝聖誕樹鏈條 effect list next = completeUnitOfWork(unitOfWork); } return next; } 複製代碼
function commitRoot(root) { const renderPriorityLevel = Scheduler.getCurrentPriorityLevel(); // 包裹一層commitRoot,commit使用Scheduler調度 Scheduler.runWithPriority(Scheduler.ImmediatePriority, commitRootImp.bind(null, root, renderPriorityLevel)); } // commit階段的入口,包括以下子階段: // before mutation階段:遍歷effect list,執行 DOM操做前觸發的鉤子 // mutation階段:遍歷effect list,執行effect function commitRootImp(root) { do { // syncCallback會保存在一個內部數組中,在 flushPassiveEffects 中 同步執行完 // 因爲syncCallback的callback是 performSyncWorkOnRoot,可能產生新的 passive effect // 因此須要遍歷直到rootWithPendingPassiveEffects爲空 flushPassiveEffects(); } while (ReactFiberCommitWorkGlobalVariables.rootWithPendingPassiveEffects !== null) if (!finishedWork) { return null; } root.finishedWork = null; root.finishedExpirationTime = NoWork; // 重置Scheduler相關 root.callbackNode = null; root.callbackExpirationTime = NoWork; root.callbackPriority = Scheduler.NoPriority; // 已經在commit階段,finishedWork對應的expirationTime對應的任務的處理已經接近尾聲 // 讓咱們找找下一個須要處理的任務 // 在 completeUnitOfWork中有childExpirationTime的冒泡邏輯 // fiber樹中高優先級的expirationTime會冒泡到頂上 // 因此 childExpirationTime 表明整棵fiber樹中下一個最高優先級的任務對應的expirationTime const remainingExpirationTimeBeforeCommit = getRemainingExpirationTime(finishedWork); // 更新root的firstPendingTime,這表明下一個要進行的任務的expirationTime markRootFinishedAtTime(root, expirationTime, remainingExpirationTimeBeforeCommit); if (root === workInProgressRoot) { // 重置 workInProgress workInProgressRoot = null; workInProgress = null; renderExpirationTime = NoWork; } let firstEffect; if (root.effectTag) { // 因爲根節點的effect list不含有自身的effect,因此當根節點自己存在effect時須要將其append 入 effect list if (finishedWork.lastEffect) { finishedWork.lastEffect.nextEffect = finishedWork; firstEffect = finishedWork.firstEffect; } else { firstEffect = finishedWork; } } else { // 根節點自己沒有effect firstEffect = finishedWork.firstEffect; } let nextEffect; if (firstEffect) { // before mutation階段 const prevExecutionContext = executionContext; executionContext |= CommitContext; nextEffect = firstEffect; do { try { nextEffect = commitBeforeMutationEffects(nextEffect); } catch(e) { console.warn('commit before error', e); nextEffect = nextEffect.nextEffect; } } while(nextEffect) // mutation階段 nextEffect = firstEffect; do { try { nextEffect = commitMutationEffects(root, nextEffect); } catch(e) { console.warn('commit mutaion error', e); nextEffect = nextEffect.nextEffect; } } while(nextEffect) // workInProgress tree 如今完成反作用的渲染變成current tree // 之因此在 mutation階段後設置是爲了componentWillUnmount觸發時 current 仍然指向以前那棵樹 root.current = finishedWork; if (ReactFiberCommitWorkGlobalVariables.rootDoesHavePassiveEffects) { // 本次commit含有passiveEffect ReactFiberCommitWorkGlobalVariables.rootDoesHavePassiveEffects = false; ReactFiberCommitWorkGlobalVariables.rootWithPendingPassiveEffects = root; ReactFiberCommitWorkGlobalVariables.pendingPassiveEffectsExpirationTime = expirationTime; ReactFiberCommitWorkGlobalVariables.pendingPassiveEffectsRenderPriority = renderPriorityLevel; } else { // effectList已處理完,GC nextEffect = firstEffect; while (nextEffect) { const nextNextEffect = nextEffect.next; nextEffect.next = null; nextEffect = nextNextEffect; } } executionContext = prevExecutionContext; } else { // 無effect root.current = finishedWork; } } 複製代碼
內容未完待續 markdown
待更新