摘要:開始寫代碼構造函數(shù)講了那么多的理論,大家一定是暈了,但是沒辦法,架構已經(jīng)比之前的簡單要復雜太多了,因此不可能指望一次性把的內(nèi)容全部理解,需要反復多看。
前言
Facebook 的研發(fā)能力真是驚人, Fiber 架構給 React 帶來了新視野的同時,將調(diào)度一詞介紹給了前端,然而這個架構實在不好懂,比起以前的 Vdom 樹,新的 Fiber 樹就麻煩太多。
可以說,React 16 和 React 15 已經(jīng)是技巧上的分水嶺,但是得益于 React 16 的 Fiber 架構,使得 React 即使在沒有開啟異步的情況下,性能依舊是得到了提高。
經(jīng)過兩個星期的痛苦研究,終于將 React 16 的渲染脈絡摸得比較清晰,可以寫文章來記錄、回顧一下。
如果你已經(jīng)稍微理解了 Fiber 架構,可以直接看代碼:倉庫地址
什么是 React Fiber ?React Fiber 并不是所謂的纖程(微線程、協(xié)程),而是一種基于瀏覽器的單線程調(diào)度算法,背后的支持 API 是大名鼎鼎的: requestIdleCallback ,得到了這個 API 的支持,我們便可以將 React 中最耗時的部分放入其中。
回顧 React 歷年來的算法都知道,reconcilation 算法實際上是一個大遞歸,大遞歸一旦進行,想要中斷還是比較不好操作的,加上頭大尾大的 React 15 代碼已經(jīng)膨脹到了不可思議的地步,在重重壓力之下,React 使用了大循環(huán)來代替之前的大遞歸,雖然代碼變得比遞歸難懂了幾個梯度,但是實際上,代碼量比原來少了非常多(開發(fā)版本 3W 行壓縮到了 1.3W 行)
那問題就來了,什么是 Fiber :一種將 recocilation (遞歸 diff ),拆分成無數(shù)個小任務的算法;它隨時能夠停止,恢復。停止恢復的時機取決于當前的一幀( 16ms )內(nèi),還有沒有足夠的時間允許計算。
React 異步渲染流程圖用戶調(diào)用 ReactDOM.render 方法,傳入例如
拿到
使用 requestIdleCallback 重復第三個步驟,直到循環(huán)到樹的所有節(jié)點
最后完成了 diff 階段,一次性將變化更新到真實 DOM 中,以防止 UI 展示的不連續(xù)性
其中,重點就是 3 和 4 階段,這兩個階段將創(chuàng)建真實 DOM 和組件渲染 ( render )拆分為無數(shù)的小碎塊,使用 requestIdleCallback 連續(xù)進行。在 React 15 的時候,渲染、創(chuàng)建、插入、刪除等操作是最費時的,在 React 16 中將渲染、創(chuàng)建抽離出來分片,這樣性能就得到了極大的提升。
那為什么更新到真實 DOM 中不能拆分呢?理論上來說,是可以拆分的,但是這會造成 UI 的不連續(xù)性,極大的影響體驗。
遞歸變成了循環(huán)以簡單的組件為例子:
從頂端的 div#root 向下走,先走左子樹
div 有兩個孩子 span ,繼續(xù)走左邊的
來到 span ,之下只有一個 hello ,到此,不再繼續(xù)往下,而是往上回到 span
因為 span 有一個兄弟,因此往兄弟 span 走去
兄弟 span 有孩子 luy ,到此,不繼續(xù)往下,而是回到 luy 的老爹 span
luy 的老爹 span 右邊沒有兄弟了,因此回到其老爹 div
div 沒有任何的兄弟,因此回到頂端的 div#root
每經(jīng)過一個 Fiber 節(jié)點,執(zhí)行 render 或者 document.createElement (或者更新 DOM )的操作
Fiber 數(shù)據(jù)結構一個 Fiber 數(shù)據(jù)結構比較復雜
const Fiber = { tag: HOST_COMPONENT, type: "div", return: parentFiber, child: childFiber, sibling: null, alternate: currentFiber, stateNode: document.createElement("div") | instance, props: { children: [], className: "foo" }, partialState: null, effectTag: PLACEMENT, effects: [] }
這是一個比較完整的 Fiber object,他復雜的原因是因為一個 Fiber 就代表了一個「正在執(zhí)行或者執(zhí)行完畢」的操作單元。這個概念不是那么好理解,如果要說得簡單一點就是:以前的 VDOM 樹節(jié)點的升級版。讓我們介紹幾個關鍵屬性:
由「 遞歸改循環(huán) 」我們可以得知,當我們循環(huán)的遍歷樹到達底部時,需要回到其父節(jié)點,那么對應的就是 Fiber 中的 return 屬性(以前叫 parent )。 child 和 sibling 類似,代表這個 Fiber 的子 Fiber 和兄弟 Fiber
stateNode 這個屬性比較特殊,用于記錄當前 Fiber 所對應的真實 DOM 節(jié)點 或者 當前虛擬組件的實例,這么做的原因第一是為了實現(xiàn) Ref ,第二是為了實現(xiàn) DOM 的跟蹤
tag 屬性在新版的 React 中一共有 14 種值,分別代表了不同的 JSX 類型。
effectTag 和 effects 這兩個屬性為的是記錄每個節(jié)點 Diff 后需要變更的狀態(tài),比如刪除,移動,插入,替換,更新等...
alternate 屬性我想拿出來多帶帶說一下,這個屬性是 Fiber 架構新加入的屬性。我們都知道,VDOM 算法是在更新的時候生成一顆新的 VDOM 樹,去和舊的進行對比。在 Fiber 架構中,當我們調(diào)用 ReactDOM.render 或者 setState 之后,會生成一顆樹叫做:work-in-progress tree,這一顆樹就是我們所謂的新樹用來與我們的舊樹進行對比,新的樹和舊的樹的 Fiber 是完全不一樣的,此時,我們就需要 alternate 屬性去鏈接新樹和舊樹。
司徒正美的研究中,一個 Fiber 和它的 alternate 屬性構成了一個聯(lián)嬰體,他們有共同的 tag ,type,stateNode 屬性,這些屬性在錯誤邊界自爆時,用于恢復當前節(jié)點。
開始寫代碼:Component 構造函數(shù)講了那么多的理論,大家一定是暈了,但是沒辦法,Fiber 架構已經(jīng)比之前的簡單 React 要復雜太多了,因此不可能指望一次性把 Fiber 的內(nèi)容全部理解,需要反復多看。
當然,結合代碼來梳理,思路舊更加清晰了。我們在構建新的架構時,老的 Luy 代碼大部分都要進行重構了,先來看看幾個主要重構的地方:
export class Component { constructor(props, context) { this.props = props this.context = context this.state = this.state || {} this.refs = {} this.updater = {} } setState(updater) { scheduleWork(this, updater) } render() { throw "should implement `render()` function" } } Component.prototype.isReactComponent = true
這就是 React.Component 的代碼
構造函數(shù)中,我們都進兩個參數(shù),一個是外部的 props ,一個是 context
內(nèi)部有 state,refs,updater, updater 用于收集 setState 的信息,便于之后更新用。當然,在這個版本之中,我并沒有使用。
setState 函數(shù)也并沒有做隊列處理,只是調(diào)用了 scheduleWork 這個函數(shù)
Component.prototype.isReactComponent = true ,這段代碼表飾著,如果一個組件的類型為 function 且擁有 isReactComponent ,那么他就是一個有狀態(tài)組件,在創(chuàng)建實例時需要用 new ,而無狀態(tài)組件只需要 fn(props,context) 調(diào)用
const tag = { HostComponent: "host", ClassComponent: "class", HostRoot: "root", HostText: 6, FunctionalComponent: 1 } const updateQueue = [] export function render(Vnode, Container, callback) { updateQueue.push({ fromTag: tag.HostRoot, stateNode: Container, props: { children: Vnode } }) requestIdleCallback(performWork) //開始干活 } export function scheduleWork(instance, partialState) { updateQueue.push({ fromTag: tag.ClassComponent, stateNode: instance, partialState: partialState }) requestIdleCallback(performWork) //開始干活 }
我們定義了一個全局變量 updateQueue 來記錄我們所有的更新操作,每當 render 和 scheduleWork (setState) 觸發(fā)時,我們都會往 updateQueue 中 push 一個狀態(tài),然后,進而調(diào)用大名鼎鼎的 requestIdleCallback 進行更新。在這里與之前的 react 15 最大不同是,更新階段和首次渲染階段得到了統(tǒng)一,都是使用了 updateQueue 進行更新。
實際上這里還有優(yōu)化的空間,就是多次 setState 的時候,應該合并成一次再進行 requestIdleCallback 的調(diào)用,不過這并不是我們的目標,我們的目標是搞懂 Fiber 架構。requestIdleCallback 調(diào)用的是 performWork 函數(shù),我們接下來看看
performWork 函數(shù)const EXPIRATION_TIME = 1 // ms async 逾期時間 let nextUnitOfWork = null let pendingCommit = null function performWork(deadline) { workLoop(deadline) if (nextUnitOfWork || updateQueue.length > 0) { requestIdleCallback(performWork) //繼續(xù)干 } } function workLoop(deadline) { if (!nextUnitOfWork) { //一個周期內(nèi)只創(chuàng)建一次 nextUnitOfWork = createWorkInProgress(updateQueue) } while (nextUnitOfWork && deadline.timeRemaining() > EXPIRATION_TIME) { nextUnitOfWork = performUnitOfWork(nextUnitOfWork) } if (pendingCommit) { //當全局 pendingCommit 變量被負值 commitAllwork(pendingCommit) } }
熟悉 requestIdleCallback 的同學一定對這兩個函數(shù)并不陌生,這兩個函數(shù)其實做的就是所謂的異步調(diào)度。
performWork 函數(shù)主要做了兩件事,第一件事就是拿到 deadline 進入我們之前所謂的大循環(huán),也就是正式進入處理新舊 Fiber 的 Diff 階段,這個階段比較的奇妙,我們叫他 workLoop 階段。workLoop 會一次處理 1 個或者多個 Fiber ,具體處理多少個,要看每一幀具體還剩下多少時間,如果一個 Fiber 消耗太多時間,那么就會等到下一幀再處理下一個 Fiber ,如此循環(huán),遍歷整個 VDOM 樹。
在這里我們注意到,如果一個 Fiber 消耗太多時間,可能會導致一幀時間的逾期,不過其實沒什么問題啦,也僅僅是一幀逾期而已,對于我們視覺上并沒有多大的影響。
workLoop 函數(shù)主要是三部曲:
createWorkInProgress 這個函數(shù)會構建一顆樹的頂端,賦值給全局變量 nextUnitOfWork ,通過迭代的方式,不斷更新 nextUnitOfWork 直到遍歷完所有樹的節(jié)點。
performUnitOfWork 函數(shù)是第二步,不斷的檢測當前幀是否還剩余時間,進行 WorkInProgress tree 的迭代
當 WorkInProgress tree 迭代完畢以后,調(diào)用 commitAllWork ,將所有的變更全部一次性的更新到 DOM 中,以保證 UI 的連續(xù)性
所有的 Diff 和創(chuàng)建真實 DOM 的操作,都在 performUnitOfWork 之中,但是插入和刪除是在 commitAllWork 之中。接下來,我們逐一分析三部曲的內(nèi)部操作。
第一步:createWorkInProgressexport function createWorkInProgress(updateQueue) { const updateTask = updateQueue.shift() if (!updateTask) return if (updateTask.partialState) { // 證明這是一個setState操作 updateTask.stateNode._internalfiber.partialState = updateTask.partialState } const rootFiber = updateTask.fromTag === tag.HostRoot ? updateTask.stateNode._rootContainerFiber : getRoot(updateTask.stateNode._internalfiber) return { tag: tag.HostRoot, stateNode: updateTask.stateNode, props: updateTask.props || rootFiber.props, alternate: rootFiber // 用于鏈接新舊的 VDOM } } function getRoot(fiber) { let _fiber = fiber while (_fiber.return) { _fiber = _fiber.return } return _fiber
這個函數(shù)的主要作用就是構建 workInProgress 樹的頂端并賦值給全局變量 nextUnitOfWork。
首先,我們先從 updateQueue 中獲取一個任務對象 updateTask 。隨后,進行判斷是否是更新階段。然后獲取 workInProgress 樹的頂端。如果是第一次渲染, RootFiber 的值是空的,因為我們并沒有構建任何的樹。
最后,我們將返回一個 Fiber 對象,這個 Fiber 對象的標識符( tag )是 HostRoot 。
第二步:performUnitOfWork// 開始遍歷 function performUnitOfWork(workInProgress) { const nextChild = beginWork(workInProgress) if (nextChild) return nextChild // 沒有 nextChild, 我們看看這個節(jié)點有沒有 sibling let current = workInProgress while (current) { //收集當前節(jié)點的effect,然后向上傳遞 completeWork(current) if (current.sibling) return current.sibling //沒有 sibling,回到這個節(jié)點的父親,看看有沒有sibling current = current.return } }
我們調(diào)用 performUnitOfWork 處理我們的 workInProgress 。
整個函數(shù)做的事情其實就是一個左遍歷樹的過程。首先,我們調(diào)用 beginWork ,獲得一個當前 Fiber 下的第一個孩子,如果有直接返回出去給 nextUnitOfWork ,當作下一個處理的節(jié)點;如果沒有找到任何孩子,證明我們已經(jīng)到達了樹的底部,通過下面的 while 循環(huán),回到當前節(jié)點的父節(jié)點,將當前 Fiber 下?lián)碛?Effect 的孩子全部記錄下來,以便于之后更新 DOM 。
然后查找當前節(jié)點的父親節(jié)點,是否有兄弟,有就返回,當成下一個處理的節(jié)點,如果沒有,就繼續(xù)回溯。
整個過程用圖來表示,就是:
在討論第三部之前,我們?nèi)匀挥袃蓚€迷惑的地方:
beginWork 是如何創(chuàng)建孩子的
completeWork 是如何收集 effect 的接下來,我們就來一起看看
beginWorkfunction beginWork(currentFiber) { switch (currentFiber.tag) { case tag.ClassComponent: { return updateClassComponent(currentFiber) } case tag.FunctionalComponent: { return updateFunctionalComponent(currentFiber) } default: { return updateHostComponent(currentFiber) } } } function updateHostComponent(currentFiber) { // 當一個 fiber 對應的 stateNode 是原生節(jié)點,那么他的 children 就放在 props 里 if (!currentFiber.stateNode) { if (currentFiber.type === null) { //代表這是文字節(jié)點 currentFiber.stateNode = document.createTextNode(currentFiber.props) } else { //代表這是真實原生 DOM 節(jié)點 currentFiber.stateNode = document.createElement(currentFiber.type) } } const newChildren = currentFiber.props.children return reconcileChildrenArray(currentFiber, newChildren) } function updateFunctionalComponent(currentFiber) { let type = currentFiber.type let props = currentFiber.props const newChildren = currentFiber.type(props) return reconcileChildrenArray(currentFiber, newChildren) } function updateClassComponent(currentFiber) { let instance = currentFiber.stateNode if (!instance) { // 如果是 mount 階段,構建一個 instance instance = currentFiber.stateNode = createInstance(currentFiber) } // 將新的state,props刷給當前的instance instance.props = currentFiber.props instance.state = { ...instance.state, ...currentFiber.partialState } // 清空 partialState currentFiber.partialState = null const newChildren = currentFiber.stateNode.render() // currentFiber 代表老的,newChildren代表新的 // 這個函數(shù)會返回孩子隊列的第一個 return reconcileChildrenArray(currentFiber, newChildren) }
beginWork 其實是一個判斷分支的函數(shù),整個函數(shù)的意思是:
判斷當前的 Fiber 是什么類型,是 class 的走 class 分支,是 stateless 的走 stateless,是原生節(jié)點的走原生分支
如果沒有 stateNode ,則創(chuàng)建一個 stateNode
如果是 class ,則創(chuàng)建實例,調(diào)用 render 函數(shù),渲染其兒子;如果是原生節(jié)點,調(diào)用 DOM API 創(chuàng)建原生節(jié)點;如果是 stateless ,就執(zhí)行它,渲染出 VDOM 節(jié)點
最后,走到最重要的函數(shù), recocileChildrenArray 函數(shù),將其每一個孩子進行鏈表的鏈接,進行 diff ,然后返回當前 Fiber 之下的第一個孩子
我們來看看比較重要的 classComponent 的構建流程
function updateClassComponent(currentFiber) { let instance = currentFiber.stateNode if (!instance) { // 如果是 mount 階段,構建一個 instance instance = currentFiber.stateNode = createInstance(currentFiber) } // 將新的state,props刷給當前的instance instance.props = currentFiber.props instance.state = { ...instance.state, ...currentFiber.partialState } // 清空 partialState currentFiber.partialState = null const newChildren = currentFiber.stateNode.render() // currentFiber 代表老的,newChildren代表新的 // 這個函數(shù)會返回孩子隊列的第一個 return reconcileChildrenArray(currentFiber, newChildren) } function createInstance(fiber) { const instance = new fiber.type(fiber.props) instance._internalfiber = fiber return instance }
如果是首次渲染,那么組件并沒有被實例話,此時我們調(diào)用 createInstance 實例化組件,然后將當前的 props 和 state 賦值給 props 、state ,隨后我們調(diào)用 render 函數(shù),獲得了新兒子 newChildren。
渲染出新兒子之后,來到了新架構下最重要的核心函數(shù) reconcileChildrenArray .
reconcileChildrenArrayconst PLACEMENT = 1 const DELETION = 2 const UPDATE = 3 function placeChild(currentFiber, newChild) { const type = newChild.type if (typeof newChild === "string" || typeof newChild === "number") { // 如果這個節(jié)點沒有 type ,這個節(jié)點就可能是 number 或者 string return createFiber(tag.HostText, null, newChild, currentFiber, PLACEMENT) } if (typeof type === "string") { // 原生節(jié)點 return createFiber(tag.HOST_COMPONENT, newChild.type, newChild.props, currentFiber, PLACEMENT) } if (typeof type === "function") { const _tag = type.prototype.isReactComponent ? tag.CLASS_COMPONENT : tag.FunctionalComponent return { type: newChild.type, tag: _tag, props: newChild.props, return: currentFiber, effectTag: PLACEMENT } } } function reconcileChildrenArray(currentFiber, newChildren) { // 對比節(jié)點,相同的標記更新 // 不同的標記 替換 // 多余的標記刪除,并且記錄下來 const arrayfiyChildren = arrayfiy(newChildren) let index = 0 let oldFiber = currentFiber.alternate ? currentFiber.alternate.child : null let newFiber = null while (index < arrayfiyChildren.length || oldFiber !== null) { const prevFiber = newFiber const newChild = arrayfiyChildren[index] const isSameFiber = oldFiber && newChild && newChild.type === oldFiber.type if (isSameFiber) { newFiber = { type: oldFiber.type, tag: oldFiber.tag, stateNode: oldFiber.stateNode, props: newChild.props, return: currentFiber, alternate: oldFiber, partialState: oldFiber.partialState, effectTag: UPDATE } } if (!isSameFiber && newChild) { newFiber = placeChild(currentFiber, newChild) } if (!isSameFiber && oldFiber) { // 這個情況的意思是新的節(jié)點比舊的節(jié)點少 // 這時候,我們要將變更的 effect 放在本節(jié)點的 list 里 oldFiber.effectTag = DELETION currentFiber.effects = currentFiber.effects || [] currentFiber.effects.push(oldFiber) } if (oldFiber) { oldFiber = oldFiber.sibling || null } if (index === 0) { currentFiber.child = newFiber } else if (prevFiber && newChild) { // 這里不懂是干嘛的 prevFiber.sibling = newFiber } index++ } return currentFiber.child }
這個函數(shù)做了幾件事
將孩子 array 化,這么做能夠使得 react 的 render 函數(shù)返回數(shù)組
currentFiber 是新的 workInProgress 上的一個節(jié)點,是屬于新的 VDOM 樹 ,而此時,我們必須要找到舊的 VDOM 樹來進行比對。那么在這里, Alternate 屬性就起到了關鍵性作用,這個屬性鏈接了舊的 VDOM ,使得我們能夠獲取原來的 VDOM
接下來我們進行對比,如果新的節(jié)點的 type 與原來的相同,那么我們將新建一個 Fiber ,標記這個 Fiber 為 UPDATE
如果新的節(jié)點的 type 與原來的不相同,那我們使用 PALCEMENT 來標記他
如果舊的節(jié)點數(shù)量比新的節(jié)點少,那就證明,我們要刪除舊的節(jié)點,我們把舊節(jié)點標記為 DELETION ,并構建一個 effect list 記錄下來
當前遍歷的是組件的第一個孩子,那么我們將他記錄在 currentFiber 的 child 字段中
當遍歷的不是第一個孩子,我們將 新建的 newFiber 用鏈表的形式將他們一起推入到 currentFiber 中
返回當前 currentFiber 下的第一個孩子
看著比較啰嗦,但是實際上做的就是構建鏈表和 diff 孩子的過程,這個函數(shù)有很多優(yōu)化的空間,使用 key 以后,在這里能提高很多的性能,為了簡單,我并沒有對 key 進行操作,之后的 Luy 版本一定會的。
completeWork: 收集 effectTag// 開始遍歷 function performUnitOfWork(workInProgress) { const nextChild = beginWork(workInProgress) if (nextChild) return nextChild // 沒有 nextChild, 我們看看這個節(jié)點有沒有 sibling let current = workInProgress while (current) { //收集當前節(jié)點的effect,然后向上傳遞 completeWork(current) if (current.sibling) return current.sibling //沒有 sibling,回到這個節(jié)點的父親,看看有沒有sibling current = current.return } } //收集有 effecttag 的 fiber function completeWork(currentFiber) { if (currentFiber.tag === tag.classComponent) { // 用于回溯最高點的 root currentFiber.stateNode._internalfiber = currentFiber } if (currentFiber.return) { const currentEffect = currentFiber.effects || [] //收集當前節(jié)點的 effect list const currentEffectTag = currentFiber.effectTag ? [currentFiber] : [] const parentEffects = currentFiber.return.effects || [] currentFiber.return.effects = parentEffects.concat(currentEffect, currentEffectTag) } else { // 到達最頂端了 pendingCommit = currentFiber } }
這個函數(shù)做了兩件事,第一件事情就是收集當前 currentFiber 的 effectTag ,將其 append 到父 Fiber 的 effectlist 中去,通過循環(huán)一層一層往上,最終到達頂端 currentFiber.return === void 666 的時候,證明我們到達了 root ,此時我們已經(jīng)把所有的 effect 收集到了頂端的 currentFiber.effect 上,并把它賦值給 pendingCommit ,進入 commitAllWork 階段。
第三步:commitAllWork終于,我們已經(jīng)通過不斷不斷的調(diào)用 requestIdleCallback 和 大循環(huán),將我們的所有變更都找出來放在了 workInProgress tree 里,我們接下來就要做最后一步:將所有的變更一次性的變更到真實 DOM 中,注意,這個階段里我們不再運行創(chuàng)建 DOM 和 render ,因此,雖然我們一次性變更所有的 DOM ,但是性能來說并不是太差。
function commitAllwork(topFiber) { topFiber.effects.forEach(f => { commitWork(f) }) topFiber.stateNode._rootContainerFiber = topFiber topFiber.effects = [] nextUnitOfWork = null pendingCommit = null }
我們直接拿到 TopFiber 中的 effects list ,遍歷,將變更全部打到 DOM 中去,然后我們將全局變量清理干凈。
function commitWork(effectFiber) { if (effectFiber.tag === tag.HostRoot) { // 代表 root 節(jié)點沒什么必要操作 return } // 拿到parent的原因是,我們要將元素插入的點,插在父親的下面 let domParentFiber = effectFiber.return while (domParentFiber.tag === tag.classComponent || domParentFiber.tag === tag.FunctionalComponent) { // 如果是 class 就直接跳過,因為 class 類型的fiber.stateNode 是其本身實例 domParentFiber = domParentFiber.return } //拿到父親的真實 DOM const domParent = domParentFiber.stateNode if (effectFiber.effectTag === PLACEMENT) { if (effectFiber.tag === tag.HostComponent || effectFiber.tag === tag.HostText) { //通過 tag 檢查是不是真實的節(jié)點 domParent.appendChild(effectFiber.stateNode) } // 其他情況 } else if (effectFiber.effectTag == UPDATE) { // 更新邏輯 只能是沒實現(xiàn) } else if (effectFiber.effectTag == DELETION) { //刪除多余的舊節(jié)點 commitDeletion(effectFiber, domParent) } } function commitDeletion(fiber, domParent) { let node = fiber while (true) { if (node.tag == tag.classComponent) { node = node.child continue } domParent.removeChild(node.stateNode) while (node != fiber && !node.sibling) { node = node.return } if (node == fiber) { return } node = node.sibling } }
這一部分代碼是最好理解的了,就是做的是刪除和插入或者更新 DOM 的操作,值得注意的是,刪除操作依舊使用的鏈表操作。
最后來一段測試代碼:
import React from "./Luy/index" import { Component } from "./component" import { render } from "./vdom" class App extends Component { state = { info: true } constructor(props) { super(props) setTimeout(() => { this.setState({ info: !this.state.info }) }, 1000) } render() { return (hello luy) } } render({this.state.info ? "imasync" : "iminfo"}, document.getElementById("root"))
我們來看看動圖吧!當節(jié)點 mount 以后,過了 1 秒,就會更新,我們簡單的更新就到此結束了
再看以下調(diào)用棧,我們的 requestIdleCallback 函數(shù)已經(jīng)正確的運行了。
如果你想下載代碼親自體驗,可以到 Luy 倉庫中:
git clone https://github.com/Foveluy/Luy.git cd Luy npm i --save-dev npm run start
目前我能找到的所有資料都放在倉庫中:資料
回顧本文幾個重要的點一開始我們就使用了一個數(shù)組來記錄 update 的信息,通過調(diào)用 requestIdleCallback 來將更新一個一個的取出來,大部分時間隊列里只有一個。
取出來以后,使用從左向右遍歷的方式,用鏈表鏈接一個一個的 Fiber ,并做 diff 和創(chuàng)建,最后一次性的 patch 到真實 DOM 中去。
現(xiàn)在 react 的架構已經(jīng)變得極其復雜,而本文也只是將 React 的整體架構通篇流程描述了一遍,里面的細節(jié)依舊值得我們的深究,比如,如何傳遞 context ,如何實現(xiàn) ref ,如何實現(xiàn)錯誤邊界處理,聲明周期的處理,這些都是很大的話題,在接下去的文章里,我會一步一步的將這些關系講清楚。
最后,感謝支持我的迷你框架項目:Luy ,現(xiàn)在正在向 Fiber 晉級!如果你喜歡,請給我一點 star
文章版權歸作者所有,未經(jīng)允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/95167.html
摘要:接下來我們就是正式的工作了,用循環(huán)從某個節(jié)點開始遍歷樹。最后一步判斷全局變量是否存在,如果存在則把這次遍歷樹產(chǎn)生的所有更新一次更新到真實的上去。 前情提要 上一篇我們提到如果 setState 之后,虛擬 dom diff 比較耗時,那么導致瀏覽器 FPS 降低,使得用戶覺得頁面卡頓。那么 react 新的調(diào)度算法就是把原本一次 diff 的過程切分到各個幀去執(zhí)行,使得瀏覽器在 dif...
摘要:因為版本將真正廢棄這三生命周期到目前為止,的渲染機制遵循同步渲染首次渲染,更新時更新時卸載時期間每個周期函數(shù)各司其職,輸入輸出都是可預測,一路下來很順暢。通過進一步觀察可以發(fā)現(xiàn),預廢棄的三個生命周期函數(shù)都發(fā)生在虛擬的構建期間,也就是之前。 showImg(https://segmentfault.com/img/bVbweoj?w=559&h=300); 背景 前段時間準備前端招聘事項...
摘要:架構理解引用原文是核心算法正在進行的重新實現(xiàn)。構建的過程就是的過程,通過來調(diào)度執(zhí)行一組任務,每完成一個任務后回來看看有沒有插隊的更緊急的,把時間控制權交還給主線程,直到下一次回調(diào)再繼續(xù)構建。 React Fiber 架構理解 引用原文:React Fiber ArchitectureReact Fiber is an ongoing reimplementation of Reacts...
摘要:在上面我們已經(jīng)知道瀏覽器是一幀一幀執(zhí)行的,在兩個執(zhí)行幀之間,主線程通常會有一小段空閑時間,可以在這個空閑期調(diào)用空閑期回調(diào),執(zhí)行一些任務。另外由于這些堆棧是可以自己控制的,所以可以加入并發(fā)或者錯誤邊界等功能。 文章首發(fā)于個人博客 前言 2016 年都已經(jīng)透露出來的概念,這都 9102 年了,我才開始寫 Fiber 的文章,表示慚愧呀。不過現(xiàn)在好的是關于 Fiber 的資料已經(jīng)很豐富了,...
摘要:在之前的叫,是新的,這次更新到架構是一次重量級的核心架構的替換,為了完成這次替換已經(jīng)準備了兩三年的時間了。因此團隊引入了異步渲染這個概念,而采用架構可以實現(xiàn)這種異步渲染的方式。官方目前已經(jīng)把和標記為,并使用新的生命周期函數(shù)和進行替換。 Diff 算法 熟悉 react 的朋友都知道,在 react 中有個核心的算法,叫 diff 算法。web 界面由 dom 樹組成,不同的 dom 樹...
閱讀 1631·2021-10-14 09:43
閱讀 5534·2021-09-07 10:21
閱讀 1279·2019-08-30 15:56
閱讀 2131·2019-08-30 15:53
閱讀 1236·2019-08-30 15:44
閱讀 2013·2019-08-30 15:44
閱讀 1323·2019-08-29 17:24
閱讀 757·2019-08-29 15:19