前言
下面相关测试例子我都写在了仓库中,可以直接拿来调试。
1、setState方法被调用(hook中是 useState中的setXXXX方法被调用)组件就会触发render,除了设置state为null的情况不会触发render。
注意注意!!
上面说的是方法被调用就会re-render,而不指的是state数据发生改变才会re-render。意思就是说如果你点击一个按钮但是一直是 this.setState({ name: ‘winne’ }),那么你点多少次组件就会re-render多少次。
2、父组件重新渲染时,内部的所有子组件默认都会重新渲染,触发render。
重新渲染render会做些什么?
1、会对新l日VNode进行对比,也就是我们所说的DoM diff。
2、对新旧两棵树进行一个深度优先遍历,这样每一个节点都会一个标记,在到深度遍历的时候,每遍历到一和个节点,就把该节点和新的节点树进行对比,如果有差异就放到一个对象里面
3、遍历差异对象,根据差异的类型,根据对应对规则更新VNode
React的处理render的基本思维模式是每次一有变动就会去重新渲染整个应用。在Virtual DOM没有出现之前,最简单的方法就是直接调用innerHTML。Virtual DOM厉害的地方并不是说它比直接操作DOM快,而是说不管数据怎么变,都会尽量以最小的代价去更新DOM。React将render函数返回的虚拟DOM树与老的进行比较,从而确定DOM要不要更新、怎么更新。当DOM树很大时,遍历两棵树进行各种比对还是相当耗性能的,特别是在顶层setState 一个微小的修改,默认会去遍历整棵树。尽管React使用高度优化的Diff 算法,但是这个过程仍然会损耗性能
。
现在分react的类组件和hook函数组件进行优化讲解。
使用 PureComponent
,每次对下一个的props和state进行一次浅比较(基本类型比较值是否相等,引用类型则比较引用地址是否相等)。当然,除了 PureComponent 外,我们还可以使用Component
配合 shouldComponentUpdate
生命周期函数进行更深层次的控制。
PureComponent具体用例好文推荐:点击这里
shouldComponentUpdate
来决定组件是否重新渲染,如果不希望组件重新渲染,在逻辑中返回false即可。
react官网性能优化篇幅:点击这里
注意噢! 函数组件的重新渲染(re-render)都会执行整个函数其内部的所有逻辑。
在一个组件中有什么东西可以跨渲染周期,也就是在组件被多次渲染之后依旧不变的属性?第一个想到的应该是state
。没错,一个组件的state可以在多次渲染之后依旧不变。但是,state的问题在于一旦修改了它就会造成组件的重新渲染,如果这个组件内部还有很多子组件,那么意味着所有的子组件也会被重新渲染
。
那么这个时候就可以使用useRef
来跨越渲染周期存储数据,而且对它修改也不会引起组件渲染
。
注意注意!!
但这并不意味了我们要全部使用useRef,因为你修改了useRef的值视图层是不会重新渲染更新的,所以如果你的变量是决定视图图层渲染的变量,请使用useState。其他用途使用useRef。
相关好文推荐:点击这里
useEffect
不加第二个参数的时候默认会在每次渲染后都执行。所以我们需要添加第二个参数列表进行控制
。
第二个参数列表中设置的变量为:每次列表中的变量改变了才会执行useEffect中的逻辑,否则就会跳过。
React.memo是React 16.6新的一个API,用来缓存组件的渲染,避免不必要的更新,其实也是一个高阶组件,与PureComponent十分类似,但不同的是,React.memo只能用于函数组件,我们的hook组件就是一个函数组件。当然 react class中也有函数组件的存在,memo语法相同
。
import React, {
useState, memo } from 'react';
const Child = (props) => {
const [value, setValue] = useState(1);
console.log('Child组件渲染了');
return (
<div>
我是子组件Child
</div>
);
};
// 不使用memo时我们一般这样导出组件
// export default Child;
// 使用memo时我们可以这样写
const compareProps = (prevProps, nextProps) => {
// 这里写入判断渲染的逻辑控制。返回true为不渲染组件,false为渲染组件;
if (prevProps.count !== nextProps.count) {
return false;
}
return true;
};
// memo接收两个参数,第一个参数是函数组件,第二个参数是比较props从而控制组件是否渲染的函数。
// 如果第二个参数不传递,则默认只会进行 props 的浅比较。
export default memo(Child, compareProps);
相关好文推荐:点击这里
上面 React.memo() 的使用我们可以发现,最终都是在最外层包装了整个组件,并且需要手动写一个方法比较那些具体的 props 不相同才进行 re-render。
而在某些场景下,我们只是希望 component 的部分
不要进行 re-render,而不是整个 component 不要 re-render,也就是要实现 局部 Pure 功能。
useMemo() 基本用法如下:
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
useMemo() 返回的是一个 memoized 值(变量值),只有当依赖项(比如上面的 a,b 发生变化的时候,才会重新计算这个 memoized 值)。
如果没有提供依赖数组(上面的 [a,b])则每次都会重新计算 memoized 值,也就会 re-render。
memoized 值不变的情况下,不会重新触发渲染逻辑。
说起渲染逻辑,需要记住的是 useMemo() 是在 render 期间执行的,所以传给 useMemo 的函数是在渲染期间运行的。不能进行一些额外的副操作
,比如网络请求等。副作用属于 useEffect,而不是 useMemo。
大白话:就是说在 Hook组件中如果你在 return 返回的jsx中使用到了一个变量,这个变量(可以是一个js变量;也可以是jsx组件)需要进行一次很昂贵的计算的话,那么就考虑使用useMemo()来缓存。
下面贴代码方便测试:
/* eslint-disable react/jsx-one-expression-per-line */
import React, {
useState, useMemo } from 'react';
import {
Button } from 'antd';
const Com = () => {
// 函数组件的每一次调用都会执行其内部的所有逻辑,那么会带来较大的性能损耗。
const [value, setValue] = useState(1);
const [name, setName] = useState('');
const initName = () => {
setName('winne');
};
const addValue = () => {
setValue