React16真是一天一改,若是如今不看,之後也很難看懂了。javascript
在React16中,雖然也是經過JSX編譯獲得一個虛擬DOM對象,但對這些虛擬DOM對象的再加工則是通過翻天覆地的變化。咱們須要追根溯底,看它是怎麼一步步轉換過來的。咱們先不看什麼組件render,先找到ReactDOM.render。在ReactDOM的源碼裏,有三個相似的東西:java
//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,這是一個內部APInode
//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對象的。react
首先DOMRenderer這個對象是由一個叫reactReconciler的方法生成,須要傳入一個對象,將一些東西注進去。最後產生一個對象,裏面就有createContainer這個方法git
// containerInfo就是ReactDOM.render(<div/>, containerInfo)的第二個對象,換言之是一個元素節點 createContainer: function (containerInfo, hydrate) { return createFiberRoot(containerInfo, hydrate); },
再看createFiberRoot是怎麼將一個真實DOM變成一個Fiber對象github
//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對象app
createFiberRoot就是建立了一個普通對象,裏面有一個current屬性引用fiber對象,有一個containerInfo屬性引用剛纔的DOM節點,而後fiber對象有一個stateNode引用剛纔的普通對象。在React15中,stateNode應該是一個組件實例或真實DOM,可能單純是爲了對齊,就建立一個普通對象。 最後返回普通對象。less
咱們先不看 DOMRenderer.unbatchedUpdates,直接看DOMRenderer.updateContainer。async
//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; }
這只是一部分更新邏輯, 簡直沒完沒了,下次繼續,添上流程圖,回憶一下本文學到的東西