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 并没有被调用。因此它们也不需要对比元素了。