You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
操作真实的DOM耗时(JS引擎和渲染引擎互斥,上下文切换耗时;可能导致重排和重绘),但是引入虚拟 DOM 也并不一定会带来更好的性能,React 官方也从来没有把虚拟 DOM 作为性能层面的卖点对外输出过。虚拟 DOM 的优越之处在于,它能够在提供更爽、更高效的研发模式的同时,仍然保持一个还不错的性能。引用尤大大的一句话:“框架给你的保证是,你在不需要手动优化的情况下,我依然可以给你提供过得去的性能。”
/**@param {String} type: 同于标识节点的类型,可以是html标签比如div等,也可以是React组件或者React Fragment类型@param {Object} config: 组件的所有属性,比如className、style等@param {Object} children: 存储的是组件标签之间嵌套的内容,也就是所谓的子节点、子元素*/functioncreateElement(type,config,children){varpropName;// 用于存储后面需要用到的元素属性varprops={};// 用于存储元素属性的键值对集合 // 定义React元素的属性key、ref、self和sourcevarkey=null;varref=null;varself=null;varsource=null;if(config!=null){// 根据入参config依次给ref、key、self和source赋值if(hasValidRef(config)){ref=config.ref;{warnIfStringRefCannotBeAutoConverted(config);}}if(hasValidKey(config)){key=''+config.key;}self=config.__self===undefined ? null : config.__self;source=config.__source===undefined ? null : config.__source;// Remaining properties are added to a new props object// 遍历config中的属性,将符合条件的属性存储到props中for(propNameinconfig){if(hasOwnProperty$1.call(config,propName)&&!RESERVED_PROPS.hasOwnProperty(propName)){props[propName]=config[propName];}}}// Children can be more than one argument, and those are transferred onto// the newly allocated props object.// 获取当前元素子节点的个数varchildrenLength=arguments.length-2;// 单个节点 ==> 直接赋值给props.childrenif(childrenLength===1){props.children=children;}elseif(childrenLength>1){// 多个子节点 ==> 声明一个子节点数组,将子节点放进该数组中,然后赋值给props.childrenvarchildArray=Array(childrenLength);for(vari=0;i<childrenLength;i++){childArray[i]=arguments[i+2];}{if(Object.freeze){Object.freeze(childArray);}}props.children=childArray;}// Resolve default props// 处理defaultPropsif(type&&type.defaultProps){vardefaultProps=type.defaultProps;for(propNameindefaultProps){if(props[propName]===undefined){props[propName]=defaultProps[propName];}}}
... // 处理key和ref// 返回reactElelemnt方法returnReactElement(type,key,ref,self,source,ReactCurrentOwner.current,props);}
如果存在跨层级的操作,React则直接判断移出子树那一层的组件消失了,对应子树需要被销毁;而移入子树的那一层新增了一个组件,需要重新为其创建一棵子树。销毁 + 重建的代价是昂贵的,因此React官方也建议开发者不要做跨层级的操作,尽量保持 DOM 结构的稳定性。
②、只有类型相同的元素才有Diff的必要
基于“若两个组件属于同一个类型,那么它们将拥有相同的DOM树形结构”这一规律,React认为,只有同类型的组件,才有进一步对比的必要性;若参与Diff的两个组件类型不同,那么直接放弃比较,原地替换掉旧的节点,如下图所示。只有确认组件类型相同后,React才会在保留组件对应 DOM 树(或子树)的基础上,尝试向更深层次去Diff。
那么 key 就可以充当每个节点的 唯一标识,有了这个标识之后,当 C 被插入到 B 和D之间时,React 会通过识别 ID,意识到D和 E 并没有发生变化,只是被调整了顺序而已。接着,React 便能够轻松地重用它“追踪”到旧的节点,将 D 和 E 转移到新的位置,并完成对 C 的插入。这样一来,同层级下元素的操作成本便大大降低。
The text was updated successfully, but these errors were encountered:
什么是虚拟
DOM
将上面的
ele
打印出来,如下所示: 这就是虚拟
DOM
在React
中的形态,是JS
和DOM
之间的一个映射缓存,它在形态上表现为一个能够描述 DOM 结构及其属性信息的 JS 对象。为什么需要虚拟
DOM
提升开发者的体验和效率
引用
React
官网中的一段话:"Virtual DOM
赋予了React
声明式的API
:您告诉React
希望让UI
是什么状态,React
就确保DOM
匹配该状态。这使您可以从属性操作、事件处理和手动DOM
更新这些在构建应用程序时必要的操作中解放出来。" 从原生
JS
到JQuery
,开发者除了关心数据之外,还需要关心DOM
的操作;接着出现了模板引擎,虽然实现了用户界面与业务数据的分离,但是它涉及到大量的字符串的拼接,开发效率也比较低下。为了解决开发体验和开发效率与DOM
操作之间的矛盾,虚拟DOM
应运而生。解决跨平台不兼容的问题
虚拟
DOM
是对真实渲染内容的一层抽象,将视图层和渲染平台解耦了,它可以是对Web
页面真实DOM
的描述,也可以是对IOS
界面、安卓界面和小程序等的描述,实现"一套代码,多端运行"性能问题?
操作真实的
DOM
耗时(JS
引擎和渲染引擎互斥,上下文切换耗时;可能导致重排和重绘),但是引入虚拟 DOM 也并不一定会带来更好的性能,React 官方也从来没有把虚拟 DOM 作为性能层面的卖点对外输出过。虚拟 DOM 的优越之处在于,它能够在提供更爽、更高效的研发模式的同时,仍然保持一个还不错的性能。引用尤大大的一句话:“框架给你的保证是,你在不需要手动优化的情况下,我依然可以给你提供过得去的性能。” 性能是一个比较复杂复杂的问题,需要在一个特定的场景下谈论才有意义,举一个极端的例子,如果数据内容整个发生了改变,此时
DOM
更新的工作量是一致的,而虚拟DOM
却会产生开销更大的JS
计算,因此此时虚拟DOM
的优势是发挥不出来的。React
中的虚拟DOM
是怎么产生的 虚拟
DOM
的产生需要借助于Babel
可以看到
JSX
标签都被编译成了React.createElement
,也就是说JSX
本质其实是React.createElement
这个JS
的语法糖,nameReact.createElement
是怎样转化成虚拟DOM
的?
createElement
的源码如下: 从上面的源码可以看出来,
createElement
的主要逻辑就是格式化数据,将开发者传入的参数转换成符合ReactElement
方法的参数。ReactElement
的源码如下: 从源码可以看出,
ReactElelemt
的主要逻辑就是把入参分装进element
对象,然后将其返回,这个element
对象就是一开始我们打印的那个虚拟DOM
。 现在可以得出的结论是,
JSX
最终被转化成了一个element
对象即虚拟DOM
。diff
算法 将前后两次的虚拟
DOM
树进行对比,定位出具体需要更新的部分,生成一个“补丁集”,这就是diff
算法。diff
算法基于以下三个规律: ①、若两个组件属于同一个类型,那么它们将拥有相同的
DOM
树结构 ②、
DOM
节点之间的跨层级操作并不多,主要是同层级操作。 ③、处于同一层级的一组子节点,可用通过设置 key 作为唯一标识,从而维持各个节点在不同渲染过程中的稳定性
Diff
算法的核心要点主要是三个: ①、分层对比
基于“
DOM
节点之间的跨层级操作并不多,同层级操作是主流”这一规律,React
的Diff
过程直接放弃了跨层级的节点比较,它只针对相同层级的节点作对比。 如果存在跨层级的操作,
React
则直接判断移出子树那一层的组件消失了,对应子树需要被销毁;而移入子树的那一层新增了一个组件,需要重新为其创建一棵子树。销毁 + 重建的代价是昂贵的,因此React
官方也建议开发者不要做跨层级的操作,尽量保持DOM
结构的稳定性。 ②、只有类型相同的元素才有
Diff
的必要 基于“若两个组件属于同一个类型,那么它们将拥有相同的
DOM
树形结构”这一规律,React
认为,只有同类型的组件,才有进一步对比的必要性;若参与Diff
的两个组件类型不同,那么直接放弃比较,原地替换掉旧的节点,如下图所示。只有确认组件类型相同后,React
才会在保留组件对应DOM
树(或子树)的基础上,尝试向更深层次去Diff
。 ③、属性
key
可以提高节点的复用性
React
官方对key
的定义是:key
是用来帮助React
识别哪些内容被更改、添加或者删除。key
需要写在用数组渲染出来的元素内部,并且需要赋予其一个稳定的值。稳定在这里很重要,因为如果key
值发生了变更,React
则会触发UI
的重渲染。这是一个非常有用的特性。 所以,
key
想解决的是同一层级中节点的重用问题。如下图所示,如果想在组件A
的两个子节点B
和D
之间插入一个新的节点C
如果没加
key
,两棵树之间的Diff
过程应该是这样的: 首先对比位于第 1 层的节点,发现两棵树的节点类型是一致的(都是
A
),于是进一步Diff
; 开始对比位于第 2 层的节点,第 1 个接受比较的是
B
这个位置,对比下来发现两棵树这个位置上的节点都是B
,继续下个节点的diff
第 2 个接受比较的是
D
这个位置,对比D
和C
,发现前后的类型不一致,直接删掉D
重建C
; 第 3 个接受比较的是
E
这个位置,对比E
和D
,发现前后的类型不一致,直接删掉E
重建D
; 最后接受比较的是树 2 的
E
节点这个位置,这个位置在树 1 里是空的,也就是说树 2 的E
是一个新增节点,所以新增一个E
。 如果有
key
,如下图所示: 那么
key
就可以充当每个节点的 唯一标识,有了这个标识之后,当C
被插入到B
和D
之间时,React
会通过识别ID
,意识到D
和E
并没有发生变化,只是被调整了顺序而已。接着,React
便能够轻松地重用它“追踪”到旧的节点,将D
和E
转移到新的位置,并完成对C
的插入。这样一来,同层级下元素的操作成本便大大降低。The text was updated successfully, but these errors were encountered: