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,true,callback); },render: function (element,callback) { //React15的重要API,逐渐退出舞台 return renderSubtreeIntoContainer(null,false,unstable_renderSubtreeIntoContainer: function (parentComponent,containerNode,callback) { //用于生成子树,废弃 return renderSubtreeIntoContainer(parentComponent,callback); } }
我们看renderSubtreeIntoContainer,这是一个内部API
//by 司徒正美, 加群:370262116 一起研究React与anujs function renderSubtreeIntoContainer(parentComponent,children,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,callback); } return DOMRenderer.getPublicRootInstance(root); }
看一下DOMRenderer.createContainer是怎么创建root对象的。
首先DOMRenderer这个对象是由一个叫reactReconciler的方法生成,需要传入一个对象,将一些东西注进去。最后产生一个对象,里面就有createContainer这个方法
// containerInfo就是ReactDOM.render(<div/>,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,internalContextTag); }; function FiberNode(tag,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来标识。
- 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,callback);
updateContainer的源码也很简单,就是获得上下文对象,决定它是叫context还是pendingContext,最后丢给scheduleTopLevelUpdate
//by 司徒正美, 加群:370262116 一起研究React与anujs updateContainer: function (element,callback) { var current = container.current;//createFiberRoot中创建的fiber对象 var context = getContextForSubtree(parentComponent); if (container.context === null) { container.context = context; } else { container.pendingContext = context; } // 原传名为 children,callback // newRoot.fiber,callback scheduleTopLevelUpdate(current,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,callback) { // // newRoot.fiber,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,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,expirationTime); requestWork(root,expirationTime); checkRootNeedsClearing(root,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; }
这只是一部分更新逻辑, 简直没完没了,下次继续,添上流程图,回忆一下本文学到的东西