React|虚拟 Dom、render函数、shouldComponentUpdate

一、虚拟Dom

虚拟Dom是介于真实Dom和你的代码之间,是对真实的 Dom 的代理映射、管理,是一种js算法结构,vue 以及 react 都采用了虚拟 Dom。因为操作真实 Dom 的时间花费(涉及到渲染引擎、DOM Tree 构建,有兴趣的可以去谷歌开发者去看,要FQ的),远远大于js代码的运行,又操作dom实际上对程序员来说十分复杂繁琐,所以虚拟Dom诞生了,它会帮助程序员管理真实dom,使得程序员可以脱离操作Dom的束缚。

React以及Vue框架都有各自的虚拟dom实现,它们都推崇组件的概念,即你不用操心dom(其实程序员也最好不要操作),直接按照它们自己的规则来编写组件就可以了,它们会帮助你管理真实效果的实现。

虚拟dom最重要的就是diff算法了,diff即difference,它的意思是会比较前后虚拟dom(在react中,涉及到state的改变会触发新的虚拟dom树生成)的差异,根据差异来有选择的操作dom(比如增删改)。

二、react中的虚拟Dom

在 React 中,一个组件会调用 render 函数产出一个React元素,React 会将之编译成一个个虚拟 Dom 的节点(通常一个节点代表了一个 dom 元素),组件可以引用其他组件,这样会慢慢的组成一个完整的虚拟 Dom Tree(类似 Dom Tree,Dom Tree 以 HTML 元素为根,这里虚拟 Dom Tree 以最初的组件产出的 React 元素为根)。这种组件生成完整虚拟 Dom Tree 的过程,我愿称之为 React 的渲染。

React每一次渲染,就会构建一次虚拟Dom Tree(第一次生成的 Dom Tree是空的,便会开始 Dom 生成),虚拟 Dom 会和之前的虚拟 Dom Tree 比较差异,如果有差异,便会做出相应的处理,这就是 React 中的虚拟 Dom 的 diff。

不知道大家有没有一个疑问,为什么每次更新 state 的时候,render 中的函数就会调用一遍?比如你在 render 中 console.log 一下,每次状态改变都会触发,这怎么回事?这就涉及到 react 组件的运作方式了,且往下看。

三、react组件生命周期

react组件生命周期

注意到,在 react 组件运行期间,发现只要shouldComponentUpdate(没有这个函数,react 组件就默认它返回 true) 函数返回 true ,组件就会调用一次 render ,这是极其浪费性能的表现。其实在React中,更改 state,会调用以该组件开始,其下的所有组件的 render函数,所以合理的设置shouldComponentUpdate ,可以做出一定的优化提升。

注意,设置为false后,新构建的虚拟 Dom 树相应部分会复用旧部分。

四、基于 shouldComponentUpdate 的优化

类组件其实可以继承React.PureCompoent,也就是所谓的纯组件,它会自动改写shouldComponentUpdate,会对组件得到的 props 和自身的 state 进行一次浅比较,浅比较即比较 props 和 state 的内存地址,相同返回 true 否则 false。我们得知道,js将对象或者数组赋予一个变量,其实这个变量引用的是他们的地址(其实大部分编程语言都是如此),修改对象或者数组,该变量不会更改。所以这里要注意更改 props 或者 state 要重新创建一个新的,而不是在其基础上修改,比如 props 是对象,需要将其拷贝进新的对象,使用Object.assign()
在这里推荐 React 自身推荐的 immutable.js ,这个库会帮助你拷贝对象或者数组,且性能很高。

函数组件可以用 React.memo()包裹达到这个效果,这就是纯函数组件
需要提醒的是,shouldComponentUpdate一旦为false,以该组件为根,之下的组件都会不去 render。

五、shouldComponentUpdate 在优化中的效果

基于shouldComponentUpdate优化的结果

说明:
  • reconciliation是React中虚拟 Dom 的优化过的diff算法,据说达到了O(n)的速度,不得不说牛逼
  • SCU 即shouldComponentUpdate,红色为返回false,绿色为true,绿色字体意味着需要更新
  • vDOMEq 即进行虚拟 Dom diff,红色代表前后不一致,绿色代表一致

以下的解释来自 React 官网

节点 C2 的 shouldComponentUpdate 返回了 false,React 因而不会去渲染 C2,也因此 C4 和 C5 的 shouldComponentUpdate 不会被调用到。
对于 C1 和 C3,shouldComponentUpdate 返回了 true,所以 React 需要继续向下查询子节点。这里 C6 的 shouldComponentUpdate 返回了 true,同时由于渲染的元素与之前的不同使得 React 更新了该 DOM。
最后一个有趣的例子是 C8。React 需要渲染这个组件,但是由于其返回的 React 元素和之前渲染的相同,所以不需要更新 DOM。
显而易见,你看到 React 只改变了 C6 的 DOM。对于 C8,通过对比了渲染的 React 元素跳过了渲染。而对于 C2 的子节点和 C7,由于 shouldComponentUpdate 使得 render 并没有被调用。因此它们也不需要对比元素了。

posted @ 2020-11-14 00:06  Sebastian·S·Pan  阅读(228)  评论(0编辑  收藏  举报