React组件性能优化
React.memo
在React的工作流中,如果只有父组件的状态发生变化,即使传递给子组件的props和子组件本身的state都没有发生变化,子组件也会重新渲染。但是从React的声明式设计理念来看,如果子组件的Props和state都没有改变,那么其生成的DOM结构和副作用也不应该发生改变。当子组件符合声明式设计理念时,就可以忽略子组件本次的Render过程。React.memo就是用来解决这个问题的。当一个组件使用memo封装后,父组件状态变更,只要传递给该子组件的props和其自身状态没变化,子组件就不会重新渲染。
建议复杂的组件、重新渲染成本高的组件都要使用memo封装后导出,比如典型的复杂表单或表格。
import React, {memo} from 'react';
const CustomTable = memo(() => {});
export default CustomTable;
useMemo、useCallback
传递给子组件的状态或者函数,如果每次都是一个新的引用,就会导致子组件经常重新渲染,即使子组件加上了memo,也会令其失效。这是因为React中判断状态是否变更使用的是浅比较,即对于对象函数这类来说只是单纯比较引用是否一致。
下面的写法都会使得子组件渲染频繁:
{xxx}} />
这个时候就需要使用useMemo来生成稳定的值,useCallback生成稳定的函数。只有当依赖发生变化的时候,才会重新计算值,防止触发不必要的渲染。
针对函数的缓存,也可以使用ahook提供的useMemoizedFn生成一个永远不会变的函数,且不需要声明依赖的状态,每次调用都会取最新依赖值。基本可以代替useCallback的使用,要注意的是useMemoizedFn所生成的函数和原函数引用完全不同,且不会继承原函数的属性,比如原型这些。
事例演示:
演示说明:
options是对象,直接写到属性中会每次生成新的值。TestDemo这类复杂组件,重新渲染成本高,建议缓存,错误示例如下
{
aa: 'aaa',
bb: 'bbb',
cc: {
dd: 'ddd'
}
}}
/
使用useMemo封装TestDemo ,对传入的对象类属性也用useMemo进行封装,正确示例如下:
// 事件监听函数直接定义,每次都是新的引用,所以用useMemoizedFn封装
const handleClick = useMemoizedFn(() => {});
const options = useMemo(() => {
return {
aa: 'aaa',
bb: 'bbb',
cc: {
dd: 'ddd'
}
}
}, []);
return (
);
使用上下文解决跨层级数据传递问题
如果使用props向下传递的方式实现跨层级数据传递,中间的组件即使没有用到该状态值,只是传递,当该状态发生变化的时候,也会触发更新。这个时候就可以使用上下文Context来进行数据传递,确保只有真正用到该值的组件重新渲染。
要注意的是使用Context通常都是多个组件共用数据的情况,对该数据的变化要遵循非必要不更新的原则,绝对不可直接传递引用值。
// 此写法每次父组件重新渲染,该值都会变化导致用到该context的子组件都会重新渲染
全局状态变化
使用recoil创建的状态为全局状态,全局状态应遵循以下规范:
1、每个状态单一职责,不要一个大对象里面放各种值
2、不应该经常变化。
3、严控数据修改范围,尽可能不暴露给使用者修改方法,避免滥用。
列表渲染每一个列表项一定要加key且唯一,不可用索引
当插入一个新的数据在最前面的时候,使用索引作为key语不加key是一样的效果,达不到优化。
批量更新state,统一渲染一次
多个state的更新有的时候是批量更新统一渲染一次的,有的时候是多次更新渲染多次的,如果把握不好更新的时机,很容易造成组件多次渲染。
在React的事件处理函数(比如点击事件)、生命周期方法(如componentDidMount)和钩子函数如(useEffect中的回调)中同步更新state的时候,React会自动批量更新,只渲染一次。
useEffect(() => {
// 生命周期中同步更新,react会批量更新
setList({...Data.list});
setInfo({...Data.info});
}, []);
const onClick = () => {
// 事件中同步更新,react会批量更新
setList({...Data.list});
setInfo({...Data.info});
}
而在其它地方、或者在上面的场景中异步更新都不会自动触发React的批量更新
useEffect(() => {
// 生命周期中异步更新,react会批量更新
setTimeout(() => {
setList({...Data.list});
setInfo({...Data.info});
})
}, []);
