在 React 中,什么情况下需要用 useCallback 和 useMemo?它们的区别是什么?

一、先理解核心问题

React 组件为什么会重复渲染?

因为:

复制代码
父组件重新执行 → 子组件默认重新执行

哪怕 props 没变。

而且:

  • 每次 render 都会重新创建函数
  • 每次 render 都会重新创建对象
  • 每次 render 都会重新执行计算

这就可能导致:

  • 子组件无意义重新渲染
  • 复杂计算重复执行
  • 性能浪费

于是有了:

复制代码
useCallback → 缓存函数
useMemo     → 缓存计算结果

二、useCallback 是什么?

缓存"函数引用"

场景:避免子组件重复渲染

看例子:

javascript 复制代码
function Parent() {
  const [count, setCount] = useState(0)

  const handleClick = () => {
    console.log("clicked")
  }

  return <Child onClick={handleClick} />
}

问题:

每次 Parent render:

ini 复制代码
handleClick = new Function()

即使逻辑没变,函数地址变了。

如果子组件是:

javascript 复制代码
const Child = React.memo(({ onClick }) => {
  console.log("child render")
  return <button onClick={onClick}>Click</button>
})

它仍然会重新渲染,因为:

yaml 复制代码
旧函数 !== 新函数

用 useCallback

javascript 复制代码
const handleClick = useCallback(() => {
  console.log("clicked")
}, [])

现在函数引用不会变。

子组件不会重复 render。


三、useMemo 是什么?

缓存"计算结果"

场景:复杂计算避免重复执行

javascript 复制代码
function App({ list }) {
  const sortedList = list.sort()  // 每次 render 都执行

  return <div>{sortedList.length}</div>
}

如果 list 很大,每次都排序很浪费。


用 useMemo

scss 复制代码
const sortedList = useMemo(() => {
  return list.sort()
}, [list])

只有 list 变了才重新计算。


四、核心区别

useCallback useMemo
缓存什么 函数 计算结果
返回值 函数 任意值
本质 useMemo 的语法糖 底层实现

实际上:

scss 复制代码
useCallback(fn, deps)

等价于:

scss 复制代码
useMemo(() => fn, deps)

五、什么时候需要 useCallback?

✅ 1. 传给 React.memo 子组件的函数

ini 复制代码
<Child onClick={handleClick} />

子组件用 React.memo 包裹时。


✅ 2. 传给 useEffect 依赖数组

scss 复制代码
useEffect(() => {
  doSomething()
}, [handleClick])

如果 handleClick 每次变,effect 每次触发。


✅ 3. 大量列表中的事件函数

例如 1000 行表格,每行一个按钮。


六、什么时候需要 useMemo?

✅ 1. 复杂计算

  • 排序
  • 过滤
  • 映射
  • 数据处理

✅ 2. 生成大对象

scss 复制代码
const config = useMemo(() => ({
  headers: {...},
  timeout: 5000
}), [])

避免对象引用变化导致子组件刷新。


✅ 3. 防止依赖变化

例如:

scss 复制代码
useEffect(() => {
  ...
}, [config])

如果 config 是新对象,每次都触发。


七、什么时候不要用(非常重要)

很多人滥用。

❌ 1. 简单函数不需要

csharp 复制代码
const add = () => a + b

没必要缓存。


❌ 2. 组件不使用 React.memo

如果子组件本身每次都会渲染,useCallback 没意义。


❌ 3. 计算很轻量

简单 map 不需要 useMemo。


八、性能误区

⚠ useCallback / useMemo 不是性能优化万能钥匙。

它们:

  • 也有性能成本
  • 也要比较依赖
  • 也会增加复杂度

React 官方建议:

先写清晰代码

真有性能问题再优化


九、实际开发经验(你这个水平该知道)

真正有用场景:

  • 大型表格
  • 数据平台
  • 编辑器
  • 可视化平台
  • 高频状态更新

基础业务系统:

基本用不到。


十、面试标准回答

可以这样说:

useCallback 用于缓存函数引用,主要用于避免因为函数重新创建导致的子组件不必要渲染,通常配合 React.memo 使用。

useMemo 用于缓存计算结果,避免复杂计算在每次渲染时重复执行。

两者本质上 useCallback 是 useMemo 的语法糖。

它们属于性能优化手段,不应该滥用,只有在存在性能瓶颈或引用稳定性需求时才使用。


十一、再给你进阶一点

React 18 之后:

  • 大多数小组件没必要手动优化
  • React Compiler 未来会自动优化
  • 过度 useCallback 反而降低可读性

一句话总结

复制代码
useCallback → 缓存函数
useMemo     → 缓存值
核心目的 → 控制引用稳定 + 避免重复计算
相关推荐
小月土星2 小时前
React Router 前端路由深度解析:从传统后端路由到现代 SPA 的完整演进
前端
程序员黑豆2 小时前
鸿蒙开发 Navigation 路由教程:从入门到实战
前端·harmonyos
烬羽4 小时前
React Router v7 完全指南:一个 Demo 吃透前端路由
javascript·react.js·前端框架
烬羽4 小时前
React Router 嵌套路由:一个 `Outlet` 引发的布局思考
react.js·面试·前端框架
用户938515635075 小时前
从零理解 React Router v6:每一个 API 都是怎么工作的
前端·javascript·全栈
用户059540174467 小时前
Redis缓存回滚踩坑实录:一个配置失误,让我在凌晨3点丢了3000条数据
前端·css
kyriewen7 小时前
别再这样写条件渲染了——你的React组件里藏着这5种定时炸弹
前端·javascript·react.js
IT_陈寒8 小时前
Redis缓存击穿把我坑惨了,原来这样设过期时间才靠谱
前端·人工智能·后端
用户938515635078 小时前
从"坐电梯"到"前端路由"——深入理解 Hash 路由原理
前端·typescript·全栈
梦想CAD控件8 小时前
网页端CAD的图形选择、编辑与夹点操作教程
前端·javascript·node.js