React Hooks中的memo、useMemo和useCallback如何使用?

近期,在我的维护工作中,我注意到 React Hooks 中的memo、useMemo和useCallback被广泛应用,但却缺乏明确区分,在代码中随处可见。尽管从性能优化的角度来看,这些 Hooks 通过缓存结果或函数的方式有效减少了不必要的计算,提高了效率,但过度或不当的使用却明显降低了代码的可读性和可维护性。有些文件中充斥着大量的useCallback实例,每个 Hook 的目的变得模糊不清:某个地方可能依赖于 A 并返回 B,而另一个地方可能是 C 依赖于 B。对于新加入的开发人员而言,这几乎等同于在代码迷宫中行走,大大增加了维护工作的复杂度。鉴于此,我对memo、useMemo和useCallback在不同场景下的使用问题进行了总结。
不使用 memo
// AppNoMemo.tsx
const AppNoMemo = () => {
console.log('no memo')
return ( <div>no memo</div> )
}
export default AppNoMemo
父组件 state 修改,父组件会重新渲染,从而导致子组件重新执行渲染。
使用 memo
import { memo } from 'react'
const AppWithMemo = () => {
console.log('memo')
return (
<div>width memo</div>
)
}
export default memo(AppWithMemo)
使用了 memo, 当父组件 state 修改,并不会导致子组件重新执行。我们可以 memo 缓存了组件。
使用 memo 且存在 props 的情况
props 简单类型
import { memo } from 'react'
const AppWithMemo = (props) => {
console.log('props')
return (
<div>props memo, {props.data}</div>
)
}
export default memo(AppWithMemo)
memo 本身会通过 Object.is 比较组件中的每个 prop 与其先前的值。注意,Object.is(3, 3) 为 true,但 Object.is({}, {}) 为 false。所以,prop 是简单类型的话,memo 的缓存组件作用是生效的。
props 非简单类型
import { memo } from 'react'
const ObjPropMemo = (props: any) => {
console.log('obj')
return (
<div>props memo, {props.data?.name}</div>
)
}
export default memo(ObjPropMemo)
props 是非简单类型,比如对象,比如函数。这个时候即使使用了 memo, 组件也不会被缓存,哪怕 props 并没有变化,父组件的重新渲染也会导致子组件重新渲染。
解决方案
方案一:最小化 props 的变化
确保组件在其 props 中接受必要的最小信息。例如,它可以接受单独的值而不是整个对象:
function Page() {
const [name, setName] = useState('Taylor');
const [age, setAge] = useState(42);
return <Profile name={name} age={age} />;
}
const Profile = memo(function Profile({ name, age }) {
// ...
});
方案二: 保证新的 prop 旧 prop 引用相同 我们可以直接使用同一个变量,这个变量可以是函数外定义的同一个变量或者使用 useState\useMemo\useCallback 缓存的变量。
const MemoProfile = memo(function Profile({data }: any) {
console.log('render')
const {name, age} = data
return <>
<div>{name}</div>
<div>{age}</div>
</>
});
// 使用同一个变量,保证保证新的 prop 旧 prop 引用相同
const profileInfo = {
name: 'Amanda',
age: 99
}
function Page() {
return <>
<MemoProfile data={profileInfo} />
</>;
}
使用 useState:
const MemoProfile = memo(function Profile({data }: any) {
console.log('render')
const {name, age} = data
return <>
<div>{name}</div>
<div>{age}</div>
</>
});
function Page() {
const [profileInfo, setrofileInfo] = useState({
name: 'Amanda',
age: 99
});
const [count, setCount] = useState(0);
return <>
<MemoProfile data={profileInfo} />
</>;
}
当然也可以使用 useMemo\useCallback,useMemo 用来缓存值,useCallback 用来缓存函数。
const cachedValue = useMemo(()=>{
...
return calculateValue;
}, dependencies)
只有 dependencies 变化的时候才会重新计算值,传给子组件的 props 可以使用 useMemo 返回的值,配合 memo 达到缓存整个子组件的效果。但是,并不是说给子组件的 props 必须使用 useMemo 来封装一下,如上所述,我们也可以使用 useState 或者函数组件外部定义的对象,来保证新的 prop 旧 prop 引用相同。在对象有明显依赖关系的时候,我们使用 useMemo 可以提高代码的可读性。
const cachedFn = useCallback(fn, dependencies)
使用 useCallback 缓存函数,同样的,不是说给子组件的函数必须使用 useCallback 封装一下,如果一个函数没有依赖项,我们完全可以使用独立函数传给子组件。
const MemoProfile = memo(function Profile({data, sayHello }: any) {
console.log('render')
const {name, age} = data
return <>
<div onClick={sayHello}>{name}</div>
<div>{age}</div>
</>
});
const sayHello = () => {
console.log('sayHello')
}
function Page() {
const [profileInfo, setrofileInfo] = useState({
name: 'Amanda',
age: 99
});
return <>
<MemoProfile data={profileInfo} sayHello={sayHello}/>
</>;
}
总结
在使用 memo 缓存子组件时,必须传递给子组件的props引用相同,否则无法实现 memo 的作用。此外,并非必须使用useMemo或useCallback来缓存传递给子组件的props,只需要确保引用相同即可。 根据 React 官网的多次提及,如果你的应用程序像该站点一样,主要是进行粗略的交互(例如直接替换页面或整个部分),通常不需要记忆化。 对于子组件渲染成本较高的情况(例如图表绘制等),考虑使用 memo、useMemo 或 useCallback 也是可行的选择。
参考链接:memo、useCallback
以上关于React Hooks中的memo、useMemo和useCallback如何使用?的文章就介绍到这了,更多相关内容请搜索码云笔记以前的文章或继续浏览下面的相关文章,希望大家以后多多支持码云笔记。
如若内容造成侵权/违法违规/事实不符,请将相关资料发送至 admin@mybj123.com 进行投诉反馈,一经查实,立即处理!
重要:如软件存在付费、会员、充值等,均属软件开发者或所属公司行为,与本站无关,网友需自行判断
码云笔记 » React Hooks中的memo、useMemo和useCallback如何使用?

微信
支付宝