您现在的位置是:首页 >技术杂谈 >react17: memo、useMemo和useCallback使用总结网站首页技术杂谈
react17: memo、useMemo和useCallback使用总结
React 所做的主要事情是让我们的 UI 与我们的 状态 保持同步,而要实现它们的同步,就需要执行一个叫做 “re-render” (重新渲染) 的操作。 从本质上,useMemo
和 useCallback
都是用来帮助我们优化 重新渲染 的工具 Hook。它们通过以下两种方式实现优化的效果。
-
减少在一次渲染中需要完成的工作量。
-
减少一个组件需要重新渲染的次数。
- memo:使用
React.memo
包裹着组件,告诉react这是纯组件,用于保护它不会受到无关状态更新的影响,即:只会在收到新数据(props)或内部状态发生变化时重新渲染。- useMemo: (1)需要进行大量计算的场景:缓存计算结果 (类似vue中的计算属性);(2)引用保留:保留某个复杂类型的引用(如:特定数组/对象变量等复杂类型,非基本数据类型)。
- useCallback:(1)引用保留:保留某个函数的引用。
注意:
useMemo
和useCallback
是一个东西,只是将返回值从 数组/对象 替换为了 函数。useCallback
是一种语法糖,它的存在存粹是为了让我们在缓存回调函数的时候可以方便点。
前言:在一个父组件中,每次父组件中的状态更改时(使用setState修改),都会触发父组件的重新渲染,从而也导致所有子组件重新渲染。
一、memo
语法:memo(函数组件,(prevProps, props)=> { ... return 布尔值 }),第2个参数为前后props的值的比较方式,判断props是否改变,默认是浅比较,返回true时,不会触发render,返回false时则会触发render。
用于解决的问题:(1)子组件没有任何props, 但是父组件每次渲染也会导致该子组件重新渲染。(2)子组件有基本数据类型props,props值都未更改,父组件渲染也会导致子组件重新渲染。
import { memo } from 'react';
const PureComponentChild = () => {
return (
...
)
}
// 使用memo包裹:只有props或自身的状态更改时,组件才会渲染。
export default memo(PureComponentChild)
小扩展:父组件中:(1)引用类型props变量:使用useState声明(memo生效)、 在组件内使用普通变量声明(memo不生效,问题原因:每次渲染重新生成新的引用问题)、在函数组件外声明的普通引用变量(memo生效)。(2)基本数据类型props变量:以上3种写法,memo都生效(即子组件都会缓存,不会重复渲染)
二、useMemo
语法: useMemo(() => { return 复杂类型变量值或函数 }, [依赖变量数组] ),返回值:任意变量
用于解决的问题:(1)子组件使用memo包裹了, 但是接收的props是"未改变"的(引用类型props,如:对象、数组和函数等变量),此时父组件渲染还是会导致该组件重新渲染。产生问题原理:复杂类型,每次父组件渲染,会生成新的引用导致。(2)某个进行大量计算的逻辑处理的计算结果,每次渲染,都会重新计算结果值,耗费性能。
子组件:
import { useMemo, memo } from 'react'
// props: 接收一个引用类型 list
const ChildA = ({ list }) => {
return (
list.map((item) => {
return <h1 key={item.name}>{item.name}</h1>;
});
)
}
export default memo(ChildA)
父组件:
import ChildA from './components/ChildA';
import { useMemo, useState } from 'react';
import { Button } from 'antd'
// 父组件
const ParentCom = () => {
const [name, setName] = useState('这是父组件默认名');
// 写法1:直接在父组件中声明复杂类型变量,父组件渲染 -》子组件也会重新渲染。
// const list = [{ name: '小明' }, { name: '小花' }];
// 写法2:使用useMemo包裹,依赖数组为空,父组件渲染 -》 子组件不会重新渲染(除非依赖数组中变量的值改变)。
const list = useMemo(() => {
return [{ name: '小明' }, { name: '小花' }];
}, []);
const modifyName = () => {
setName('新名' + Date.now());
};
return (
<>
<h1>父组件名: {name}</h1>
<Button onClick={modifyName} />
<ChildA list={list} />;
</>
);
};
export default ParentCom;
三、useCallback
语法:useCallback(() => { 函数内部的处理逻辑 }, [依赖变量数组] ),返回值:是一个函数(引用)。
用于解决的问题:(1)子组件使用memo包裹,props有传函数,每次父组件更新,即使函数未改变,也会重新渲染问题。
import { useCallback, useMemo } from 'react'
// useCallback方式
const modifyParentNameFun = useCallback(() => {
... // 函数内部自定义处理逻辑
},[])
// useMemo方式:跟使用useCallback是等效的,一样的功能!2者选一即可!
const modifyParentNameFun = useMemo(() => {
return function() [
... // 函数内部自定义处理逻辑
}
},[])
<ChildA modifyParentNameFun={modifyParentNameFun}>