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

目录
文章目录隐藏
  1. 不使用 memo
  2. 使用 memo
  3. 使用 memo 且存在 props 的情况
  4. 总结

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

近期,在我的维护工作中,我注意到 React Hooks 中的memouseMemouseCallback被广泛应用,但却缺乏明确区分,在代码中随处可见。尽管从性能优化的角度来看,这些 Hooks 通过缓存结果或函数的方式有效减少了不必要的计算,提高了效率,但过度或不当的使用却明显降低了代码的可读性和可维护性。有些文件中充斥着大量的useCallback实例,每个 Hook 的目的变得模糊不清:某个地方可能依赖于 A 并返回 B,而另一个地方可能是 C 依赖于 B。对于新加入的开发人员而言,这几乎等同于在代码迷宫中行走,大大增加了维护工作的复杂度。鉴于此,我对memouseMemouseCallback在不同场景下的使用问题进行了总结。

不使用 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\useCallbackuseMemo 用来缓存值,useCallback 用来缓存函数。

const cachedValue = useMemo(()=>{
  ...
  return calculateValue;
}, dependencies)

只有 dependencies 变化的时候才会重新计算值,传给子组件的 props 可以使用 useMemo 返回的值,配合 memo 达到缓存整个子组件的效果。但是,并不是说给子组件的 props 必须使用 useMemo 来封装一下,如上所述,我们也可以使用 useState 或者函数组件外部定义的对象,来保证新的 propprop 引用相同。在对象有明显依赖关系的时候,我们使用 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 的作用。此外,并非必须使用useMemouseCallback来缓存传递给子组件的props,只需要确保引用相同即可。 根据 React 官网的多次提及,如果你的应用程序像该站点一样,主要是进行粗略的交互(例如直接替换页面或整个部分),通常不需要记忆化。 对于子组件渲染成本较高的情况(例如图表绘制等),考虑使用 memo、useMemo 或 useCallback 也是可行的选择。

参考链接:memouseCallback

「点点赞赏,手留余香」

0

给作者打赏,鼓励TA抓紧创作!

微信微信 支付宝支付宝

还没有人赞赏,快来当第一个赞赏的人吧!

声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系maynote@foxmail.com处理
码云笔记 » React Hooks中的memo、useMemo和useCallback如何使用?

发表回复