本篇文章作为react源码分析与优化写作计划的第一篇,分析了react是如何创建vdom和fiber tree的。本篇文章通过阅读react 16.8及以上版本源码以及参考大量分析文章写作而成,react框架本身算法以及架构层也是不断的在优化,所以源码中存在很多legacy
的方法,不过这并不影响我们对于react设计思想的学习和理解。
阅读源码一定要带着目的性的去展开,这样就会减少过程中的枯燥感,而写作能够提炼和升华自己的学习和理解,这也是本篇以及后续文章的动力所在。如果写作的文章还能够帮助到其他开发者,那就更好了。
首先,来看一个简单的 React 组件。
import React from 'react';
export default function App() {
return (
<div className="App">
<h1>Hello React</h1>
</div>
);
}
上面常用的语法称之为 JSX,是 React.createElement
方法的语法糖,使用 JSX 能够直观的展现 UI 及其交互,实现关注点分离。
每个 react 组件的顶部都要导入 React,因为 JSX 实际上依赖 Babel(@babel/preset-react)来对语法进行转换,最终生成React.createElemnt
的嵌套语法。
下方能够直观的看到 JSX 转换后的渲染结果。
function App() {
return React.createElement(
'div',
{
className: 'App',
},
React.createElement('h1', null, 'Hello React')
);
}
createElement()
方法定义如下:
React.createElement(type, [props], [...children]);
createElement()
接收三个参数,分别是元素类型、属性值以及子元素,它最终会生成 Virtual DOM。
我们将上面的<App />
组件内容打印到控制台中。
可以看到 Virtual DOM 本质上是 JS 对象,将节点信息通过键值对的方式存储起来,同时使用嵌套来表示节点间的层级关系。使用 VDOM 能够避免频繁的进行 DOM 操作,同时也为后面的 React Diff 算法创造了条件。现在回到createElement()
方法,来看一下它究竟是如何生产 VDOM 的。
createElement()方法精简版(v16.8)
createElement
首先,createElement()
方法会先通过遍历config
获取所有的参数,然后获取其子节点以及默认的props
的值。然后将值传递给ReactElement()
调用并返回 JS 对象。
ReactElement
值得注意的是,每个 react 组件都会使用$$typeof
来标识,它的值使用了Symbol
数据结构来确保唯一性。
到目前为止,我们得到了 VDOM,react通过协调算法(reconciliation)去比较更新前后的VDOM,从而找到需要更新的最小操作,减少了浏览器多次操作DOM的成本。但是,由于使用递归的方式来遍历组件树,当组件树越来越大,递归遍历的成本就越高。这样,由于持续占用主线程,像布局、动画等任务无法立即得到处理,就会出现丢帧的现象。所以,为不同类型的任务赋予优先级,同时支持任务的暂停、中止与恢复,是非常有必要的。
为了解决上面存在的问题,React团队给出了React Fiber算法以及fiber tree数据结构(基于单链表的树结构),而ReactDOM.render
方法就是实现React Fiber算法以及构建fiber tree的核心API。
render()
方法定义如下:
ReactDOM.render(element, container[, callback])
这里重点从源码层面讲解下ReactDOM.render
是如何构建fiber tree的。
ReactDOM.render
实际调用了legacyRenderSubtreeIntoContainer
方法,调用过程以及传参如下:
ReactDOM = {
render(element, container, callback) {
return legacyRenderSubtreeIntoContainer(
null,
element,
container,
false,
callback
);
},
};
其中的element
和container
我们都很熟悉了,而callback
是用来渲染完成后需要执行的回调函数。再来看看该方法的定义。
function legacyRenderSubtreeIntoContainer(
parentComponent,
children,
container,
forceHydrate,
callback
) {
let root = container._reactRootContainer;
let fiberRoot;
// 初次渲染
if (!root) {
// 初始化挂载,获得React根容器对象
root = container._reactRootContainer = legacyCreateRootFromDOMContainer(
container,
forceHydrate
);
fiberRoot = root._internalRoot;
// 初始化安装不需要批量更新,需要尽快完成
unbatchedUpdates(() => {
updateContainer(children, fiberRoot, parentComponent, callback);
});
} else {
fiberRoot = root._internalRoot;
updateContainer(children, fiberRoot, parentComponent, callback);
}
return getPublicRootInstance(fiberRoot);
}
上面是简化后的源码。先来看传参,因为是挂载root
,所以parentComponent
设置为null
。另外一个参数forceHydrate
代表是否是服务端渲染,因为调用的render()
方法为客服端渲染,所以默认为false
。另外callback
使用少,所以关于它的处理过程就省略了。
因为是首次挂载,所以root
从container._reactRootContainer
获取不到值,就会创建FiberRoot
对象。在FiberRoot
对象创建过程中考虑到了服务端渲染的情况,并且函数之间相互调用非常多,所以这里直接展示其最终调用的核心方法。
// 创建fiberRoot和rootFiber并相互引用
function createFiberRoot(containerInfo, tag, hydrate, hydrationCallbacks) {
const root = new FiberRootNode(containerInfo, tag, hydrate);
if (enableSuspenseCallback) {
root.hydrationCallbacks = hydrationCallbacks;
}
// 创建fiber tree的根节点,即rootFiber
const uninitializedFiber = createHostRootFiber(tag);
root.current = uninitializedFiber;
uninitializedFiber.stateNode = root;
initializeUpdateQueue(uninitializedFiber);
return root;
}
在该方法中containerInfo
就是root
节点,而tag
为FiberRoot
节点的标记,这里为LegacyRoot
。另外两个参数和服务端渲染有关。这里使用FiberRootNode
方法创建了FiberRoot
对象,并使用createHostRootFiber
方法创建RootFiber
对象,使FiberRoot
中的current
指向RootFiber
,RootFiber
的stateNode
指向FiberRoot
,形成相互引用。
下面的两个构造函数是展现出了fiberRoot以及rootFiber的部分重要的属性。
FiberRootNode部分属性:
function FiberRootNode(containerInfo, tag, hydrate) {
// 用于标记fiberRoot的类型
this.tag = tag;
// 指向当前激活的与之对应的rootFiber节点
this.current = null;
// 和fiberRoot关联的DOM容器的相关信息
this.containerInfo = containerInfo;
// 当前的fiberRoot是否处于hydrate模式
this.hydrate = hydrate;
// 每个fiberRoot实例上都只会维护一个任务,该任务保存在callbackNode属性中
this.callbackNode = null;
// 当前任务的优先级
this.callbackPriority = NoPriority;
}
Fiber Node构造函数的部分属性:
function FiberNode(tag, pendingProps, key, mode) {
// rootFiber指向fiberRoot,child fiber指向对应的组件实例
this.stateNode = null;
// return属性始终指向父节点
this.return = null;
// child属性始终指向第一个子节点
this.child = null;
// sibling属性始终指向第一个兄弟节点
this.sibling = null;
// 表示更新队列,例如在常见的setState操作中,会将需要更新的数据存放到updateQueue队列中用于后续调度
this.updateQueue = null;
// 表示当前更新任务的过期时间,即在该时间之后更新任务将会被完成
this.expirationTime = NoWork;
}
最终生成的fiber tree结构示意图如下:
fiber树结构示意图
react 并不会比原生操作 DOM 快,但是在大型应用中,往往不需要每次全部重新渲染,这时 react 通过 VDOM 以及 diff 算法能够只更新必要的 DOM。react 将 VDOM 与 diff 算法结合起来并对其进行优化,提供了高性能的 React Diff 算法,通过一系列的策略,将传统的 diff 算法复杂度 O(n^3)优化为 O(n)的复杂度,极大的提升了渲染性能。
这里不展开探究 React Diff 的具体实现原理,而先了解下它到底的基于什么策略来实现的。
基于这三个策略,react 在 tree diff 和 component diff 中,两棵树只会对同层次的节点进行比较。如果同层级的树发生了更新,则会将该节点及其子节点同时进行更新,这样避免了递归遍历更加深入的节点的操作。在后面渲染性能优化部分,对于同一类型的组件如果能够准确的知道 VDOM 是否变化,使用shouldComponentUpdate
来判断该组件是否需要 diff,能够节省大量的 diff 运算时间。
当 react 进行 element diff 操作中,在元素中添加唯一的key
来进行区分,对其进行算法优化。所以像大数据量的列表之类的组件中最好添加key
属性,能够带来一定的性能提升。
本文由哈喽比特于2年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/f1AHGOosON-GHTrDO_99Gg
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。