MST

星途 面试题库

面试题:React 性能优化之 useMemo 与 useCallback 的深度理解

在一个复杂的 React 应用中,有多层嵌套组件,且父组件频繁更新。在子组件中有一个函数和一个计算值需要优化。你如何确定是使用 useMemo 来包裹计算值,还是用 useCallback 来包裹函数?如果错误使用,可能会导致什么性能问题?请详细分析。
27.0万 热度难度
前端开发React

知识考点

AI 面试

面试题答案

一键面试

1. 使用 useMemo 还是 useCallback 的判断依据

  • useMemo 用于计算值:当计算值是一个复杂的操作,例如涉及大量数据的处理、复杂的算法运算等,且这个计算值在依赖不变的情况下不应该重新计算时,使用 useMemo。它会返回一个 memoized 值,只有在其依赖数组中的值发生变化时,才会重新计算。例如,计算一个大型数组的总和,每次父组件更新时都重新计算这个总和是不必要的,使用 useMemo 可以避免这种不必要的计算。
const sum = useMemo(() => {
  let total = 0;
  for (let i = 0; i < largeArray.length; i++) {
    total += largeArray[i];
  }
  return total;
}, [largeArray]);
  • useCallback 用于函数:当子组件依赖一个函数引用,并且希望在依赖不变时函数引用不发生变化,使用 useCallback。这在将函数作为 prop 传递给子组件,且子组件依赖于函数引用的稳定性(例如 shouldComponentUpdate 判断,或者 React.memo 依赖函数引用相等性来决定是否重新渲染)时非常有用。例如,子组件内部有一个按钮,点击按钮调用传递进来的函数,如果每次父组件更新都导致函数引用变化,可能会触发不必要的子组件重新渲染。
const handleClick = useCallback(() => {
  // 处理点击逻辑
}, []);

2. 错误使用导致的性能问题

  • 错误使用 useMemo 包裹函数
    • useMemo 返回的是一个值,虽然函数也是值的一种,但它的本意并非用于 memoize 函数引用。如果错误使用,函数每次调用都需要先从 memoized 值中获取,这可能引入不必要的间接性。
    • 由于 useMemo 旨在缓存计算值,它的依赖数组检查可能与 useCallback 不同。如果依赖设置不当,可能导致函数过早或过晚更新,从而破坏组件的逻辑,例如导致子组件依赖的函数引用变化不及时,影响交互逻辑。
  • 错误使用 useCallback 包裹计算值
    • useCallback 主要用于 memoize 函数引用,它不会像 useMemo 那样缓存计算结果。所以每次组件渲染时,都会重新执行包裹的表达式来获取值,这就失去了缓存复杂计算结果的意义,导致性能损耗,例如每次渲染都重新计算大型数组的总和,而不是复用之前的计算结果。
    • 此外,useCallback 返回的是函数,若将其当作普通值使用,可能会在使用时需要额外的函数调用操作,造成代码逻辑的混乱和潜在的性能问题。