React16真是一天一改,如果现在不看,以后也很难看懂了。

在React16中,虽然也是通过JSX编译得到一个虚拟DOM对象,但对这些虚拟DOM对象的再加工则是经过翻天覆地的变化。我们需要追根溯底,看它是怎么一步步转换过来的。我们先不看什么组件render,先找到ReactDOM.render。在ReactDOM的源码里,有三个类似的东西:

//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,逐渐退出舞台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与anujsfunction 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(<div/>, containerInfo)的第二个对象,换言之是一个元素节点
createContainer: function (containerInfo, hydrate) {return createFiberRoot(containerInfo, hydrate);
},

再看createFiberRoot是怎么将一个真实DOM变成一个Fiber对象

//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对象都是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为之前的根组件,现在它为nullDOMRenderer.updateContainer(children, newRoot, parentComponent, callback);

updateContainer的源码也很简单,就是获得上下文对象,决定它是叫context还是pendingContext,最后丢给scheduleTopLevelUpdate

//by 司徒正美, 加群:370262116 一起研究React与anujsupdateContainer: 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, callbackscheduleTopLevelUpdate(current, element, callback);},

getContextForSubtree的实现

//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是两个全新的概念,
// 从原上下文中抽取一部分出来
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与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);//计算过时时间}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 欢迎加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是执行虚拟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;
}
//用于调整渲染顺序,高优先级的组件先执行
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;}

这只是一部分更新逻辑, 简直没完没了,下次继续,添上流程图,回忆一下本文学到的东西

React16.2的fiber架构相关推荐

  1. 转载:React Fiber架构(浅显易懂)

    性能优化是一个系统性的工程,如果只看到局部,引入算法,当然是越快越好; 但从整体来看,在关键点引入缓存,可以秒杀N多算法,或另辟蹊径,探索事件的本质,可能用户要的并不是快-- React16启用了全新 ...

  2. 手写简易版 React 来彻底搞懂 fiber 架构

    React 16 之前和之后最大的区别就是 16 引入了 fiber,又基于 fiber 实现了 hooks.整天都提 fiber,那 fiber 到底是啥?它和 vdom 是什么关系? 与其看各种解 ...

  3. 02-react Fiber 架构

    原文链接 Fiber 是对 React 核心算法的重构,facebook 团队使用两年多的时间去重构 React 的核心算法,在React16 以上的版本中引入了 Fiber 架构: ​ 为什么需要 ...

  4. react 日期怎么格式化_手写React的Fiber架构,深入理解其原理

    熟悉React的朋友都知道,React支持jsx语法,我们可以直接将HTML代码写到JS中间,然后渲染到页面上,我们写的HTML如果有更新的话,React还有虚拟DOM的对比,只更新变化的部分,而不重 ...

  5. React系列——React Fiber 架构介绍资料汇总(翻译+中文资料)

    原文 react-fiber-architecture 介绍 React Fibre是React核心算法正在进行的重新实现.它是React团队两年多的研究成果. React Fiber的目标是提高其对 ...

  6. 前端React教程第六课 认识栈调和、setState和Fiber架构

    10 React 中的"栈调和"(Stack Reconciler)过程是怎样的? 时下 React 16 乃至 React 17 都是业界公认的"当红炸子鸡" ...

  7. 理解React框架的Fiber架构

    React为什么要使用Fiber 架构 以下内容引用修言的<深入浅出搞定react>课程,该文通俗易懂,由浅入深,一步步剖析react原理,有兴趣的朋友可以去拉钩教育前端模块找他的课程,附 ...

  8. react源码中的fiber架构

    先看一下FiberNode在源码中的样子 FiberNode // packages/react-reconciler/src/ReactFiber.old.js function FiberNode ...

  9. Fiber架构的简单理解与实现

    来源 | http://www.fly63.com/article/detial/9789 1.简介 本文主要理解fiber的基本原理.为了能够更好的理解fiber原理,我们会从零开始构建一个简单的r ...

最新文章

  1. MXNET源码中TShape值的获取和打印
  2. HDFS的Java客户端操作代码(查看HDFS下的文件是否存在)
  3. MAT之PCA:利用PCA(四个主成分的贡献率就才达100%)降维提高测试集辛烷值含量预测准确度并《测试集辛烷值含量预测结果对比》
  4. JDK7的日期时间类
  5. Java 8 Friday Goodies:轻松派本地缓存
  6. 2017年什么命_所谓“佛系”,真的不是什么都不做!
  7. 天池 在线编程 删除字符(单调栈)
  8. python 文件指针及文件覆盖
  9. 阶段3 3.SpringMVC·_01.SpringMVC概述及入门案例_02.SpringMVC框架的介绍
  10. 内容下沉新时代:在一二线做品质,去三四线接地气
  11. 百度学术打开不了?怎么办
  12. Wordpress 网站添加打赏插件
  13. 目前最赚钱的5种计算机编程语言
  14. 密码重置用HTML怎么弄,如何用简单命令重置路由器密码
  15. 安卓App自启动,两种不同的方式!!!支持到安卓4.4
  16. aecc2019能装saber吗_【2018年10月重磅】After Effects CC2019 分享新功能介绍
  17. static修饰的特点
  18. 答题小程序 服务器,答题小程序如何实现随机发题目
  19. Lua string字符串相关操作
  20. 基于CloudSim 的云资源调度系统分析设计与实现——合肥工业大学云计算课程作业

热门文章

  1. Broadcom NetXtrem II网卡Linux安装指南(转载)
  2. 一次遍历等概率选取字符串中的某个字符
  3. UA MATH563 概率论的数学基础 中心极限定理15 Kolmogorov 0-1律
  4. UA MATH574M 统计学习II 高维数据的二元分类
  5. 在一个sql分组查询中使用多个聚集函数
  6. C# main函数的返回值 - 译
  7. Win32 树视图控件的基本使用
  8. codeplex网站翻译
  9. js/jquery中实现图片轮播
  10. 使用密码记录工具keepass来保存密码