问答文章1 问答文章501 问答文章1001 问答文章1501 问答文章2001 问答文章2501 问答文章3001 问答文章3501 问答文章4001 问答文章4501 问答文章5001 问答文章5501 问答文章6001 问答文章6501 问答文章7001 问答文章7501 问答文章8001 问答文章8501 问答文章9001 问答文章9501

react渲染原理分析

发布网友 发布时间:2022-09-14 01:25

我来回答

1个回答

热心网友 时间:2024-11-17 11:28

欢迎进我的 blog 查看更多react相关的文章😁

本篇文章作为react源码分析与优化写作计划的第一篇,分析了react是如何创建vdom和fiber tree的。本篇文章通过阅读react 16.8及以上版本源码以及参考大量分析文章写作而成,react框架本身算法以及架构层也是不断的在优化,所以源码中存在很多 legacy 的方法,不过这并不影响我们对于react设计思想的学习和理解。

阅读源码一定要带着目的性的去展开,这样就会减少过程中的枯燥感,而写作能够提炼和升华自己的学习和理解,这也是本篇以及后续文章的动力所在。如果写作的文章还能够帮助到其他开发者,那就更好了。

首先,来看一个简单的 React 组件。

上面常用的语法称之为 JSX,是 React.createElement 方法的语法糖,使用 JSX 能够直观的展现 UI 及其交互,实现关注点分离。

每个 react 组件的顶部都要导入 React,因为 JSX 实际上依赖 Babel(@babel/preset-react)来对语法进行转换,最终生成 React.createElemnt 的嵌套语法。

下方能够直观的看到 JSX 转换后的渲染结果。

createElement() 方法定义如下:

createElement() 接收三个参数,分别是元素类型、属性值以及子元素,它最终会生成 Virtual DOM。

我们将上面的 <App /> 组件内容打印到控制台中。

可以看到 Virtual DOM 本质上是 JS 对象,将节点信息通过键值对的方式存储起来,同时使用嵌套来表示节点间的层级关系。使用 VDOM 能够避免频繁的进行 DOM 操作,同时也为后面的 React Diff 算法创造了条件。现在回到 createElement() 方法,来看一下它究竟是如何生产 VDOM 的。

createElement()方法精简版(v16.8)

首先, createElement() 方法会先通过遍历 config 获取所有的参数,然后获取其子节点以及默认的 props 的值。然后将值传递给 ReactElement() 调用并返回 JS 对象。

值得注意的是,每个 react 组件都会使用 $$typeof 来标识,它的值使用了 Symbol 数据结构来确保唯一性。

到目前为止,我们得到了 VDOM,react通过协调算法(reconciliation)去比较更新前后的VDOM,从而找到需要更新的最小操作,减少了浏览器多次操作DOM的成本。但是,由于使用递归的方式来遍历组件树,当组件树越来越大,递归遍历的成本就越高。这样,由于持续占用主线程,像布局、动画等任务无法立即得到处理,就会出现丢帧的现象。所以,为不同类型的任务赋予优先级,同时支持任务的暂停、中止与恢复,是非常有必要的。

为了解决上面存在的问题,React团队给出了React Fiber算法以及fiber tree数据结构(基于单链表的树结构),而 ReactDOM.render 方法就是实现React Fiber算法以及构建fiber tree的核心API。

render() 方法定义如下:

这里重点从源码层面讲解下 ReactDOM.render 是如何构建fiber tree的。

ReactDOM.render 实际调用了 legacyRenderSubtreeIntoContainer 方法,调用过程以及传参如下:

其中的 element 和 container 我们都很熟悉了,而 callback 是用来渲染完成后需要执行的回调函数。再来看看该方法的定义。

上面是简化后的源码。先来看传参,因为是挂载 root ,所以 parentComponent 设置为 null 。另外一个参数 forceHydrate 代表是否是服务端渲染,因为调用的 render() 方法为客服端渲染,所以默认为 false 。另外 callback 使用少,所以关于它的处理过程就省略了。

因为是首次挂载,所以 root 从 container._reactRootContainer 获取不到值,就会创建 FiberRoot 对象。在 FiberRoot 对象创建过程中考虑到了服务端渲染的情况,并且函数之间相互调用非常多,所以这里直接展示其最终调用的核心方法。

在该方法中 containerInfo 就是 root 节点,而 tag 为 FiberRoot 节点的标记,这里为 LegacyRoot 。另外两个参数和服务端渲染有关。这里使用 FiberRootNode 方法创建了 FiberRoot 对象,并使用 createHostRootFiber 方法创建 RootFiber 对象,使 FiberRoot 中的 current 指向 RootFiber , RootFiber 的 stateNode 指向 FiberRoot ,形成相互引用。

下面的两个构造函数是展现出了fiberRoot以及rootFiber的部分重要的属性。

FiberRootNode部分属性:

Fiber Node构造函数的部分属性:

最终生成的fiber tree结构示意图如下:

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 属性,能够带来一定的性能提升。

声明声明:本网页内容为用户发布,旨在传播知识,不代表本网认同其观点,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。E-MAIL:11247931@qq.com
同龄人早发育好还是晚发育好 小孩晚熟正常吗? 女孩子身体发育的早晚跟童子身有关系吗? 自喷漆如何晾干 自喷漆一般几分钟能干 自动静电喷塑流水线 玫瑰茉莉薄荷茶有什么功效 平面磨床哪家的好 十大名牌平面磨床 手机病毒查杀软件推荐选择最好的手机病毒查杀软件 vue渲染原理 浏览器渲染原理 小牧mzy2020款式怎么样 项链上有MZY是什么牌子?大概什么价位?? mzy是什么饮料 统计数据漏报会处罚吗 这两个哪个是无线网卡? 螺钉的长度是从螺钉的什么地方算起的介绍 联想笔记本怎样查找mck 苹果5 id账号忘记了怎么办 路由器wan口可以直接接电脑么? 路由器网线直接插电脑上能用吗 东莞属于哪个省哪个市 东莞是哪个省哪个市呢 如何使用lm385获得1.2v的电压 385-2 POAS是什么芯片 ad385主板ic芯片发热怎么修 暗黑破坏神2装备孔数分不清?多方面带你理清影响装备孔数的因素_百度知... 暗黑二八目鳗头为什么好 少女前线 70级以上的人形不能强化吗 色彩构成主要学习什么求大神帮助 皮包染色怎么去除 ghost怎么念? 捕捉猪獾子夹子哪里有卖? 如何提高幼儿园服务 幼儿园“课后延时服务”,解决了孩子接送问题,家长却仍不满意 公司投影坏了,老板派我采购会议平板,大家能推荐一款质量过硬、实用靠谱... 我想上一下高中时的同学录,可是我不清楚ID地址,请问可以查到吗 国民革命军第二十路军的介绍 长津湖战役后对26军的处理是什么 舞厅里面接吻有什么影响 舞厅里的情缘 做包子馒头5斤面粉酵母泡打粉比率是多少 夏天晚上几点能钓鱼吗 婚礼场面舞 钢琴谱 求有指法的《婚礼场面舞》(杜鸣心)钢琴谱,急!!! 求有指法的婚礼场面舞钢琴谱 求曲谱,钢琴的。都Ancient City II 线上收款可以到账对公户吗? 怎么养金针菇的根 做扇贝粉丝的时候,应该开水入锅蒸还是冷水入锅蒸?蒸多久比较合适呢...