React16.2的fiber架构
React16真是一天一改,如果現(xiàn)在不看,以后也很難看懂了。
在React16中,雖然也是通過(guò)JSX編譯得到一個(gè)虛擬DOM對(duì)象,但對(duì)這些虛擬DOM對(duì)象的再加工則是經(jīng)過(guò)翻天覆地的變化。我們需要追根溯底,看它是怎么一步步轉(zhuǎn)換過(guò)來(lái)的。我們先不看什么組件render,先找到ReactDOM.render。在ReactDOM的源碼里,有三個(gè)類似的東西:
//by 司徒正美, 加群:370262116 一起研究React與anujs // https://github.com/RubyLouvre/anu 歡迎加starReactDOM= {hydrate: function (element, container, callback) {//新API,代替renderreturn renderSubtreeIntoContainer(null, element, container, true, callback);},render: function (element, container, callback) {//React15的重要API,逐漸退出舞臺(tái)return renderSubtreeIntoContainer(null, element, container, false, callback);},unstable_renderSubtreeIntoContainer: function (parentComponent, element, containerNode, callback) {//用于生成子樹,廢棄return renderSubtreeIntoContainer(parentComponent, element, containerNode, false, callback);} }我們看renderSubtreeIntoContainer,這是一個(gè)內(nèi)部API
//by 司徒正美, 加群:370262116 一起研究React與anujsfunction renderSubtreeIntoContainer(parentComponent, children, container, forceHydrate, callback) {var root = container._reactRootContainer;if (!root) {//如果是第一次對(duì)這個(gè)元素進(jìn)行渲染,那么它會(huì)清空元素的內(nèi)部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);//創(chuàng)建一個(gè)HostRoot對(duì)象,是Fiber對(duì)象的一種root = container._reactRootContainer = newRoot;// Initial mount should not be batched.DOMRenderer.unbatchedUpdates(function () {//對(duì)newRoot對(duì)象進(jìn)行更新DOMRenderer.updateContainer(children, newRoot, parentComponent, callback);});} else {//對(duì)root對(duì)象進(jìn)行更新DOMRenderer.updateContainer(children, root, parentComponent, callback);}return DOMRenderer.getPublicRootInstance(root); }看一下DOMRenderer.createContainer是怎么創(chuàng)建root對(duì)象的。
首先DOMRenderer這個(gè)對(duì)象是由一個(gè)叫reactReconciler的方法生成,需要傳入一個(gè)對(duì)象,將一些東西注進(jìn)去。最后產(chǎn)生一個(gè)對(duì)象,里面就有createContainer這個(gè)方法
// containerInfo就是ReactDOM.render(<div/>, containerInfo)的第二個(gè)對(duì)象,換言之是一個(gè)元素節(jié)點(diǎn) createContainer: function (containerInfo, hydrate) {return createFiberRoot(containerInfo, hydrate); },再看createFiberRoot是怎么將一個(gè)真實(shí)DOM變成一個(gè)Fiber對(duì)象
//by 司徒正美, 加群:370262116 一起研究React與anujsfunction 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) {// Instancethis.tag = tag;this.key = key;this.type = null;this.stateNode = null;// Fiberthis['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;// Effectsthis.effectTag = NoEffect;this.nextEffect = null;this.firstEffect = null;this.lastEffect = null;this.expirationTime = NoWork;this.alternate = null;}所有Fiber對(duì)象都是FiberNode的實(shí)例,它有許多種類型,通過(guò)tag來(lái)標(biāo)識(shí)。
內(nèi)部有許多方法來(lái)生成Fiber對(duì)象
- createFiberFromElement (type為類,無(wú)狀態(tài)函數(shù),元素標(biāo)簽名)
- createFiberFromFragment (type為React.Fragment)
- createFiberFromText (在JSX中表現(xiàn)為字符串,數(shù)字)
- createFiberFromHostInstanceForDeletion
- createFiberFromCall
- createFiberFromReturn
- createFiberFromPortal (createPortal就會(huì)產(chǎn)生該類型)
- createFiberRoot (用于ReactDOM.render的根節(jié)點(diǎn))
createFiberRoot就是創(chuàng)建了一個(gè)普通對(duì)象,里面有一個(gè)current屬性引用fiber對(duì)象,有一個(gè)containerInfo屬性引用剛才的DOM節(jié)點(diǎn),然后fiber對(duì)象有一個(gè)stateNode引用剛才的普通對(duì)象。在React15中,stateNode應(yīng)該是一個(gè)組件實(shí)例或真實(shí)DOM,可能單純是為了對(duì)齊,就創(chuàng)建一個(gè)普通對(duì)象。 最后返回普通對(duì)象。
我們先不看 DOMRenderer.unbatchedUpdates,直接看DOMRenderer.updateContainer。
//children就是ReactDOM的第一個(gè)參數(shù),children通常表示一個(gè)數(shù)組,但是現(xiàn)在它泛指各種虛擬DOM了,第二個(gè)對(duì)象就是剛才提到的普通對(duì)象,我們可以稱它為根組件,parentComponent為之前的根組件,現(xiàn)在它為nullDOMRenderer.updateContainer(children, newRoot, parentComponent, callback);updateContainer的源碼也很簡(jiǎn)單,就是獲得上下文對(duì)象,決定它是叫context還是pendingContext,最后丟給scheduleTopLevelUpdate
//by 司徒正美, 加群:370262116 一起研究React與anujsupdateContainer: function (element, container, parentComponent, callback) {var current = container.current;//createFiberRoot中創(chuàng)建的fiber對(duì)象var context = getContextForSubtree(parentComponent);if (container.context === null) {container.context = context;} else {container.pendingContext = context;}// 原傳名為 children, newRoot, parentComponent, callback// newRoot.fiber, children, callbackscheduleTopLevelUpdate(current, element, callback);},getContextForSubtree的實(shí)現(xiàn)
//by 司徒正美, 加群:370262116 一起研究React與anujsfunction getContextForSubtree(parentComponent) {if (!parentComponent) {return emptyObject_1;}var fiber = get(parentComponent);var parentContext = findCurrentUnmaskedContext(fiber);return isContextProvider(fiber) ? processChildContext(fiber, parentContext) : parentContext; } //isContextConsumer與isContextProvider是兩個(gè)全新的概念, // 從原上下文中抽取一部分出來(lái) function isContextConsumer(fiber) {return fiber.tag === ClassComponent && fiber.type.contextTypes != null; } //isContextProvider,產(chǎn)生一個(gè)新的上下文 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; }因?yàn)槲覀兊膒arentComponent一開始不存在,于是返回一個(gè)空對(duì)象。注意,這個(gè)空對(duì)象是重復(fù)使用的,不是每次返回一個(gè)新的空對(duì)象,這是一個(gè)很好的優(yōu)化。
scheduleTopLevelUpdate是將用戶的傳參封裝成一個(gè)update對(duì)象, update對(duì)象有partialState對(duì)象,它就是相當(dāng)于React15中 的setState的第一個(gè)state傳參。但現(xiàn)在partialState中竟然把children放進(jìn)去了。
//by 司徒正美, 加群:370262116 一起研究React與anujsfunction scheduleTopLevelUpdate(current, element, callback) {// // newRoot.fiber, children, callbackcallback = 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);//計(jì)算過(guò)時(shí)時(shí)間}var update = {expirationTime: expirationTime,//過(guò)時(shí)時(shí)間partialState: { element: element },//!!!!神奇callback: callback,isReplace: false,isForced: false,nextCallback: null,next: null};insertUpdateIntoFiber(current, update);//創(chuàng)建一個(gè)列隊(duì)scheduleWork(current, expirationTime);//執(zhí)行列隊(duì)}列隊(duì)是一個(gè)鏈表
//by 司徒正美, 加群:370262116 一起研究React與anujs // https://github.com/RubyLouvre/anu 歡迎加starfunction 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 emptyqueue.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是執(zhí)行虛擬DOM(fiber樹)的更新。 scheduleWork,requestWork, performWork是三部曲。
//by 司徒正美, 加群:370262116 一起研究React與anujsfunction 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, commitRootif (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; } //用于調(diào)整渲染順序,高優(yōu)先級(jí)的組件先執(zhí)行 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 higherhighestPriorityWork = 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;}這只是一部分更新邏輯, 簡(jiǎn)直沒完沒了,下次繼續(xù),添上流程圖,回憶一下本文學(xué)到的東西
總結(jié)
以上是生活随笔為你收集整理的React16.2的fiber架构的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: arcgis python 保存当前窗口
- 下一篇: 【364天】跃迁之路——程序员高效学习方