摘要:司徒正美,加群一起研究与用于调整渲染顺序,高优先级的组件先执行这只是一部分更新逻辑,简直没完没了,下次继续添上流程图,回忆一下本文学到的东西
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; }
这只是一部分更新逻辑, 简直没完没了,下次继续,添上流程图,回忆一下本文学到的东西
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/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 ...
阅读 1736·2021-09-26 09:46
阅读 3014·2021-09-22 15:55
阅读 2605·2019-08-30 14:17
阅读 3025·2019-08-26 11:59
阅读 1806·2019-08-26 11:35
阅读 3152·2019-08-26 10:45
阅读 3150·2019-08-23 18:28
阅读 1099·2019-08-23 18:21