摘要:司徒正美,加群一起研究與用于調整渲染順序,高優先級的組件先執行這只是一部分更新邏輯,簡直沒完沒了,下次繼續添上流程圖,回憶一下本文學到的東西
React16真是一天一改,如果現在不看,以后也很難看懂了。
在React16中,雖然也是通過JSX編譯得到一個虛擬DOM對象,但對這些虛擬DOM對象的再加工則是經過翻天覆地的變化。我們需要追根溯底,看它是怎么一步步轉換過來的。我們先不看什么組件render,先找到ReactDOM.render。在ReactDOM的源碼里,有三個類似的東西:
//by 司徒正美, 加群:370262116 一起研究React與anujs // https://github.com/RubyLouvre/anu 歡迎加star ReactDOM= { hydrate: function (element, container, callback) { //新API,代替render return renderSubtreeIntoContainer(null, element, container, true, callback); }, render: function (element, container, callback) { //React15的重要API,逐漸退出舞臺 return renderSubtreeIntoContainer(null, element, container, false, callback); }, unstable_renderSubtreeIntoContainer: function (parentComponent, element, containerNode, callback) { //用于生成子樹,廢棄 return renderSubtreeIntoContainer(parentComponent, element, containerNode, false, callback); } }
我們看renderSubtreeIntoContainer,這是一個內部API
//by 司徒正美, 加群:370262116 一起研究React與anujs function renderSubtreeIntoContainer(parentComponent, children, container, forceHydrate, callback) { var root = container._reactRootContainer; if (!root) { //如果是第一次對這個元素進行渲染,那么它會清空元素的內部 var shouldHydrate = forceHydrate || shouldHydrateDueToLegacyHeuristic(container); // First clear any existing content. if (!shouldHydrate) { var warned = false; var rootSibling = void 0; while (rootSibling = container.lastChild) { container.removeChild(rootSibling); } } var newRoot = DOMRenderer.createContainer(container, shouldHydrate); //創建一個HostRoot對象,是Fiber對象的一種 root = container._reactRootContainer = newRoot; // Initial mount should not be batched. DOMRenderer.unbatchedUpdates(function () { //對newRoot對象進行更新 DOMRenderer.updateContainer(children, newRoot, parentComponent, callback); }); } else { //對root對象進行更新 DOMRenderer.updateContainer(children, root, parentComponent, callback); } return DOMRenderer.getPublicRootInstance(root); }
看一下DOMRenderer.createContainer是怎么創建root對象的。
首先DOMRenderer這個對象是由一個叫reactReconciler的方法生成,需要傳入一個對象,將一些東西注進去。最后產生一個對象,里面就有createContainer這個方法
// containerInfo就是ReactDOM.render(, containerInfo)的第二個對象,換言之是一個元素節點 createContainer: function (containerInfo, hydrate) { return createFiberRoot(containerInfo, hydrate); },
再看createFiberRoot是怎么將一個真實DOM變成一個Fiber對象
//by 司徒正美, 加群:370262116 一起研究React與anujs function createFiberRoot(containerInfo, hydrate) { // Cyclic construction. This cheats the type system right now because // stateNode is any. var uninitializedFiber = createHostRootFiber(); var root = { current: uninitializedFiber, containerInfo: containerInfo, pendingChildren: null, remainingExpirationTime: NoWork, isReadyForCommit: false, finishedWork: null, context: null, pendingContext: null, hydrate: hydrate, nextScheduledRoot: null }; uninitializedFiber.stateNode = root; return root; } function createHostRootFiber() { var fiber = createFiber(HostRoot, null, NoContext); return fiber; } var createFiber = function (tag, key, internalContextTag) { return new FiberNode(tag, key, internalContextTag); }; function FiberNode(tag, key, internalContextTag) { // Instance this.tag = tag; this.key = key; this.type = null; this.stateNode = null; // Fiber this["return"] = null; this.child = null; this.sibling = null; this.index = 0; this.ref = null; this.pendingProps = null; this.memoizedProps = null; this.updateQueue = null; this.memoizedState = null; this.internalContextTag = internalContextTag; // Effects this.effectTag = NoEffect; this.nextEffect = null; this.firstEffect = null; this.lastEffect = null; this.expirationTime = NoWork; this.alternate = null; }
所有Fiber對象都是FiberNode的實例,它有許多種類型,通過tag來標識。
內部有許多方法來生成Fiber對象
createFiberFromElement (type為類,無狀態函數,元素標簽名)
createFiberFromFragment (type為React.Fragment)
createFiberFromText (在JSX中表現為字符串,數字)
createFiberFromHostInstanceForDeletion
createFiberFromCall
createFiberFromReturn
createFiberFromPortal (createPortal就會產生該類型)
createFiberRoot (用于ReactDOM.render的根節點)
createFiberRoot就是創建了一個普通對象,里面有一個current屬性引用fiber對象,有一個containerInfo屬性引用剛才的DOM節點,然后fiber對象有一個stateNode引用剛才的普通對象。在React15中,stateNode應該是一個組件實例或真實DOM,可能單純是為了對齊,就創建一個普通對象。 最后返回普通對象。
我們先不看 DOMRenderer.unbatchedUpdates,直接看DOMRenderer.updateContainer。
//children就是ReactDOM的第一個參數,children通常表示一個數組,但是現在它泛指各種虛擬DOM了,第二個對象就是剛才提到的普通對象,我們可以稱它為根組件,parentComponent為之前的根組件,現在它為null DOMRenderer.updateContainer(children, newRoot, parentComponent, callback);
updateContainer的源碼也很簡單,就是獲得上下文對象,決定它是叫context還是pendingContext,最后丟給scheduleTopLevelUpdate
//by 司徒正美, 加群:370262116 一起研究React與anujs updateContainer: function (element, container, parentComponent, callback) { var current = container.current;//createFiberRoot中創建的fiber對象 var context = getContextForSubtree(parentComponent); if (container.context === null) { container.context = context; } else { container.pendingContext = context; } // 原傳名為 children, newRoot, parentComponent, callback // newRoot.fiber, children, callback scheduleTopLevelUpdate(current, element, callback); },
getContextForSubtree的實現
//by 司徒正美, 加群:370262116 一起研究React與anujs function getContextForSubtree(parentComponent) { if (!parentComponent) { return emptyObject_1; } var fiber = get(parentComponent); var parentContext = findCurrentUnmaskedContext(fiber); return isContextProvider(fiber) ? processChildContext(fiber, parentContext) : parentContext; } //isContextConsumer與isContextProvider是兩個全新的概念, // 從原上下文中抽取一部分出來 function isContextConsumer(fiber) { return fiber.tag === ClassComponent && fiber.type.contextTypes != null; } //isContextProvider,產生一個新的上下文 function isContextProvider(fiber) { return fiber.tag === ClassComponent && fiber.type.childContextTypes != null; } function _processChildContext(currentContext) { var Component = this._currentElement.type; var inst = this._instance; var childContext; if (inst.getChildContext) { childContext = inst.getChildContext(); } if (childContext) { return _assign({}, currentContext, childContext); } return currentContext; } function findCurrentUnmaskedContext(fiber) { var node = fiber; while (node.tag !== HostRoot) { if (isContextProvider(node)) { return node.stateNode.__reactInternalMemoizedMergedChildContext; } var parent = node["return"]; node = parent; } return node.stateNode.context; }
因為我們的parentComponent一開始不存在,于是返回一個空對象。注意,這個空對象是重復使用的,不是每次返回一個新的空對象,這是一個很好的優化。
scheduleTopLevelUpdate是將用戶的傳參封裝成一個update對象, update對象有partialState對象,它就是相當于React15中 的setState的第一個state傳參。但現在partialState中竟然把children放進去了。
//by 司徒正美, 加群:370262116 一起研究React與anujs function scheduleTopLevelUpdate(current, element, callback) { // // newRoot.fiber, children, callback callback = callback === undefined ? null : callback; var expirationTime = void 0; // Check if the top-level element is an async wrapper component. If so, // treat updates to the root as async. This is a bit weird but lets us // avoid a separate `renderAsync` API. if (enableAsyncSubtreeAPI && element != null && element.type != null && element.type.prototype != null && element.type.prototype.unstable_isAsyncReactComponent === true) { expirationTime = computeAsyncExpiration(); } else { expirationTime = computeExpirationForFiber(current);//計算過時時間 } var update = { expirationTime: expirationTime,//過時時間 partialState: { element: element },//!!!!神奇 callback: callback, isReplace: false, isForced: false, nextCallback: null, next: null }; insertUpdateIntoFiber(current, update);//創建一個列隊 scheduleWork(current, expirationTime);//執行列隊 }
列隊是一個鏈表
//by 司徒正美, 加群:370262116 一起研究React與anujs // https://github.com/RubyLouvre/anu 歡迎加star function insertUpdateIntoFiber(fiber, update) { // We"ll have at least one and at most two distinct update queues. var alternateFiber = fiber.alternate; var queue1 = fiber.updateQueue; if (queue1 === null) { // TODO: We don"t know what the base state will be until we begin work. // It depends on which fiber is the next current. Initialize with an empty // base state, then set to the memoizedState when rendering. Not super // happy with this approach. queue1 = fiber.updateQueue = createUpdateQueue(null); } var queue2 = void 0; if (alternateFiber !== null) { queue2 = alternateFiber.updateQueue; if (queue2 === null) { queue2 = alternateFiber.updateQueue = createUpdateQueue(null); } } else { queue2 = null; } queue2 = queue2 !== queue1 ? queue2 : null; // If there"s only one queue, add the update to that queue and exit. if (queue2 === null) { insertUpdateIntoQueue(queue1, update); return; } // If either queue is empty, we need to add to both queues. if (queue1.last === null || queue2.last === null) { insertUpdateIntoQueue(queue1, update); insertUpdateIntoQueue(queue2, update); return; } // If both lists are not empty, the last update is the same for both lists // because of structural sharing. So, we should only append to one of // the lists. insertUpdateIntoQueue(queue1, update); // But we still need to update the `last` pointer of queue2. queue2.last = update; } function insertUpdateIntoQueue(queue, update) { // Append the update to the end of the list. if (queue.last === null) { // Queue is empty queue.first = queue.last = update; } else { queue.last.next = update; queue.last = update; } if (queue.expirationTime === NoWork || queue.expirationTime > update.expirationTime) { queue.expirationTime = update.expirationTime; } }
scheduleWork是執行虛擬DOM(fiber樹)的更新。 scheduleWork,requestWork, performWork是三部曲。
//by 司徒正美, 加群:370262116 一起研究React與anujs function scheduleWork(fiber, expirationTime) { return scheduleWorkImpl(fiber, expirationTime, false); } function checkRootNeedsClearing(root, fiber, expirationTime) { if (!isWorking && root === nextRoot && expirationTime < nextRenderExpirationTime) { // Restart the root from the top. if (nextUnitOfWork !== null) { // This is an interruption. (Used for performance tracking.) interruptedBy = fiber; } nextRoot = null; nextUnitOfWork = null; nextRenderExpirationTime = NoWork; } } function scheduleWorkImpl(fiber, expirationTime, isErrorRecovery) { recordScheduleUpdate(); var node = fiber; while (node !== null) { // Walk the parent path to the root and update each node"s // expiration time. if (node.expirationTime === NoWork || node.expirationTime > expirationTime) { node.expirationTime = expirationTime; } if (node.alternate !== null) { if (node.alternate.expirationTime === NoWork || node.alternate.expirationTime > expirationTime) { node.alternate.expirationTime = expirationTime; } } if (node["return"] === null) { if (node.tag === HostRoot) { var root = node.stateNode; checkRootNeedsClearing(root, fiber, expirationTime); requestWork(root, expirationTime); checkRootNeedsClearing(root, fiber, expirationTime); } else { return; } } node = node["return"]; } } function requestWork(root, expirationTime) { if (nestedUpdateCount > NESTED_UPDATE_LIMIT) { invariant_1(false, "Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops."); } // Add the root to the schedule. // Check if this root is already part of the schedule. if (root.nextScheduledRoot === null) { // This root is not already scheduled. Add it. root.remainingExpirationTime = expirationTime; if (lastScheduledRoot === null) { firstScheduledRoot = lastScheduledRoot = root; root.nextScheduledRoot = root; } else { lastScheduledRoot.nextScheduledRoot = root; lastScheduledRoot = root; lastScheduledRoot.nextScheduledRoot = firstScheduledRoot; } } else { // This root is already scheduled, but its priority may have increased. var remainingExpirationTime = root.remainingExpirationTime; if (remainingExpirationTime === NoWork || expirationTime < remainingExpirationTime) { // Update the priority. root.remainingExpirationTime = expirationTime; } } if (isRendering) { // Prevent reentrancy. Remaining work will be scheduled at the end of // the currently rendering batch. return; } if (isBatchingUpdates) { // Flush work at the end of the batch. if (isUnbatchingUpdates) { // unless we"re inside unbatchedUpdates, in which case we should // flush it now. nextFlushedRoot = root; nextFlushedExpirationTime = Sync; performWorkOnRoot(nextFlushedRoot, nextFlushedExpirationTime); } return; } // TODO: Get rid of Sync and use current time? if (expirationTime === Sync) { performWork(Sync, null); } else { scheduleCallbackWithExpiration(expirationTime); } } function performWork(minExpirationTime, dl) { deadline = dl; // Keep working on roots until there"s no more work, or until the we reach // the deadline. findHighestPriorityRoot(); if (enableUserTimingAPI && deadline !== null) { var didExpire = nextFlushedExpirationTime < recalculateCurrentTime(); stopRequestCallbackTimer(didExpire); } while (nextFlushedRoot !== null && nextFlushedExpirationTime !== NoWork && (minExpirationTime === NoWork || nextFlushedExpirationTime <= minExpirationTime) && !deadlineDidExpire) { performWorkOnRoot(nextFlushedRoot, nextFlushedExpirationTime); // Find the next highest priority work. findHighestPriorityRoot(); } // We"re done flushing work. Either we ran out of time in this callback, // or there"s no more work left with sufficient priority. // If we"re inside a callback, set this to false since we just completed it. if (deadline !== null) { callbackExpirationTime = NoWork; callbackID = -1; } // If there"s work left over, schedule a new callback. if (nextFlushedExpirationTime !== NoWork) { scheduleCallbackWithExpiration(nextFlushedExpirationTime); } // Clean-up. deadline = null; deadlineDidExpire = false; nestedUpdateCount = 0; if (hasUnhandledError) { var _error4 = unhandledError; unhandledError = null; hasUnhandledError = false; throw _error4; } } function performWorkOnRoot(root, expirationTime) { !!isRendering ? invariant_1(false, "performWorkOnRoot was called recursively. This error is likely caused by a bug in React. Please file an issue.") : void 0; isRendering = true; // Check if this is async work or sync/expired work. // TODO: Pass current time as argument to renderRoot, commitRoot if (expirationTime <= recalculateCurrentTime()) { // Flush sync work. var finishedWork = root.finishedWork; if (finishedWork !== null) { // This root is already complete. We can commit it. root.finishedWork = null; root.remainingExpirationTime = commitRoot(finishedWork); } else { root.finishedWork = null; finishedWork = renderRoot(root, expirationTime); if (finishedWork !== null) { // We"ve completed the root. Commit it. root.remainingExpirationTime = commitRoot(finishedWork); } } } else { // Flush async work. var _finishedWork = root.finishedWork; if (_finishedWork !== null) { // This root is already complete. We can commit it. root.finishedWork = null; root.remainingExpirationTime = commitRoot(_finishedWork); } else { root.finishedWork = null; _finishedWork = renderRoot(root, expirationTime); if (_finishedWork !== null) { // We"ve completed the root. Check the deadline one more time // before committing. if (!shouldYield()) { // Still time left. Commit the root. root.remainingExpirationTime = commitRoot(_finishedWork); } else { // There"s no time left. Mark this root as complete. We"ll come // back and commit it later. root.finishedWork = _finishedWork; } } } } isRendering = false; } //用于調整渲染順序,高優先級的組件先執行 function findHighestPriorityRoot() { var highestPriorityWork = NoWork; var highestPriorityRoot = null; if (lastScheduledRoot !== null) { var previousScheduledRoot = lastScheduledRoot; var root = firstScheduledRoot; while (root !== null) { var remainingExpirationTime = root.remainingExpirationTime; if (remainingExpirationTime === NoWork) { // This root no longer has work. Remove it from the scheduler. // TODO: This check is redudant, but Flow is confused by the branch // below where we set lastScheduledRoot to null, even though we break // from the loop right after. !(previousScheduledRoot !== null && lastScheduledRoot !== null) ? invariant_1(false, "Should have a previous and last root. This error is likely caused by a bug in React. Please file an issue.") : void 0; if (root === root.nextScheduledRoot) { // This is the only root in the list. root.nextScheduledRoot = null; firstScheduledRoot = lastScheduledRoot = null; break; } else if (root === firstScheduledRoot) { // This is the first root in the list. var next = root.nextScheduledRoot; firstScheduledRoot = next; lastScheduledRoot.nextScheduledRoot = next; root.nextScheduledRoot = null; } else if (root === lastScheduledRoot) { // This is the last root in the list. lastScheduledRoot = previousScheduledRoot; lastScheduledRoot.nextScheduledRoot = firstScheduledRoot; root.nextScheduledRoot = null; break; } else { previousScheduledRoot.nextScheduledRoot = root.nextScheduledRoot; root.nextScheduledRoot = null; } root = previousScheduledRoot.nextScheduledRoot; } else { if (highestPriorityWork === NoWork || remainingExpirationTime < highestPriorityWork) { // Update the priority, if it"s higher highestPriorityWork = remainingExpirationTime; highestPriorityRoot = root; } if (root === lastScheduledRoot) { break; } previousScheduledRoot = root; root = root.nextScheduledRoot; } } } // If the next root is the same as the previous root, this is a nested // update. To prevent an infinite loop, increment the nested update count. var previousFlushedRoot = nextFlushedRoot; if (previousFlushedRoot !== null && previousFlushedRoot === highestPriorityRoot) { nestedUpdateCount++; } else { // Reset whenever we switch roots. nestedUpdateCount = 0; } nextFlushedRoot = highestPriorityRoot; nextFlushedExpirationTime = highestPriorityWork; }
這只是一部分更新邏輯, 簡直沒完沒了,下次繼續,添上流程圖,回憶一下本文學到的東西
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/107189.html
摘要:為了幫助理解,我們繼續加日志司徒正美,加群一起研究與只要收到更新對象,就會被調度程序調用。渲染器在將來的某個時刻調用。導步肯定為歡迎加繼續略也是怒長,代碼的特點是許多巨型類,巨型方法,有之遺風。 insertUpdateIntoFiber 會根據fiber的狀態創建一個或兩個列隊對象,對象是長成這樣的 //by 司徒正美, 加群:370262116 一起研究React與anujs //...
摘要:引言于發布版本,時至今日已更新到,且引入了大量的令人振奮的新特性,本文章將帶領大家根據更新的時間脈絡了解的新特性。其作用是根據傳遞的來更新。新增等指針事件。 1 引言 于 2017.09.26 Facebook 發布 React v16.0 版本,時至今日已更新到 React v16.6,且引入了大量的令人振奮的新特性,本文章將帶領大家根據 React 更新的時間脈絡了解 React1...
摘要:因為版本將真正廢棄這三生命周期到目前為止,的渲染機制遵循同步渲染首次渲染,更新時更新時卸載時期間每個周期函數各司其職,輸入輸出都是可預測,一路下來很順暢。通過進一步觀察可以發現,預廢棄的三個生命周期函數都發生在虛擬的構建期間,也就是之前。 showImg(https://segmentfault.com/img/bVbweoj?w=559&h=300); 背景 前段時間準備前端招聘事項...
摘要:它的主體特征是增量渲染能夠將渲染工作分割成塊,并將其分散到多個幀中。實際上,這樣做可能會造成浪費,導致幀丟失并降低用戶體驗。當一個函數被執行時,一個新的堆棧框架被添加到堆棧中。該堆棧框表示由該函數執行的工作。 原文 react-fiber-architecture 介紹 React Fibre是React核心算法正在進行的重新實現。它是React團隊兩年多的研究成果。 React ...
閱讀 1739·2021-09-26 09:46
閱讀 3017·2021-09-22 15:55
閱讀 2608·2019-08-30 14:17
閱讀 3026·2019-08-26 11:59
閱讀 1809·2019-08-26 11:35
閱讀 3155·2019-08-26 10:45
閱讀 3151·2019-08-23 18:28
閱讀 1105·2019-08-23 18:21