likes
comments
collection
share

memo、useMemo、useCallback到底怎么用呢

作者站长头像
站长
· 阅读数 50

最近维护一些老项目,我发现memouseMemo以及useCallback这些React Hooks被广泛但不加区分地使用,“四处横飞”。虽然从性能优化的角度看,它们通过缓存结果或函数的确减少了不必要的计算,从而提升了效率,但过度或不当的使用显著降低了代码的可读性和可维护性。有些文件中充斥着大量的useCallback实例,每个Hook的意图变得模糊不清:一处可能依赖于A并返回B,另一处则可能是C依赖于B。这对于新加入的开发者来说,几乎等同于代码层面的“迷宫”,极大增加了维护工作的复杂度。基于此,我梳理了一下 memouseMemo以及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 }) {
  // ...
});

方案二: 保证新的 propprop 引用相同 我们可以直接使用同一个变量,这个变量可以是函数外定义的同一个变量或者使用 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 不起作用。同时,传给子组件的 props不必非得使用 useMemo 或者 useCallback 来缓存,只要保证引用相同即可。

“如果你的应用像此站点一样,大多数交互是粗略的(例如直接替换页面或整个部分),那么通常不需要记忆化。”

这段话在 React 官网出现过多次,也就是大多数情况下我们可能并不需要使用 memouseMemouseCallback 来帮我们做缓存。在子组件渲染成本比较高(比如图表绘制等。)的情况下再考虑使用memouseMemouseCallback 也不迟。

zh-hans.react.dev/reference/r…

zh-hans.react.dev/reference/r…

转载自:https://juejin.cn/post/7371820718462894131
评论
请登录