从 Vue3 到 React · 快速上手系列第 12 篇:并发特性与性能优化

面向 Vue3 开发者的 React 快速上手系列

Vue 里你天天用 computed------依赖变了自动重算、自动缓存,几乎不用操心性能。React 里对应的是 useMemo,但心智完全不同 :因为 React 组件每次渲染都重跑整个函数,React 需要你显式告诉它什么时候可以复用上次结果


一、先破除一个误区:大多数"派生值"根本不需要 useMemo

在 Vue,几乎所有派生数据你都会写成 computed。但在 React,普通计算直接写就行,因为每次渲染重新算一遍通常很快:

jsx 复制代码
function Demo({ firstName, lastName, price, qty }) {
  // ✅ 直接算,不需要任何 Hook
  const fullName = `${firstName} ${lastName}`
  const total = price * qty
  const isExpensive = total > 1000

  return <p>{fullName}:{total} 元</p>
}

对标 Vue 你会写三个 computed,但在 React 这些都是廉价计算,直接写反而更清晰。不要一上来就给所有派生值套 useMemo------那是过度优化。


二、useMemo:只为"昂贵计算"缓存

只有当计算真的很昂贵 (大数组排序/过滤、复杂运算)时,才用 useMemo 缓存结果,避免每次渲染都重算:

jsx 复制代码
import { useMemo } from 'react'

function ProductList({ products, keyword }) {
  // 昂贵的过滤+排序,只在 products 或 keyword 变化时重算
  const visible = useMemo(() => {
    return products
      .filter(p => p.name.includes(keyword))
      .sort((a, b) => b.sales - a.sales)
  }, [products, keyword])   // ← 依赖数组,等价于 computed 的依赖

  return <ul>{visible.map(p => <li key={p.id}>{p.name}</li>)}</ul>
}

对标 Vue:

js 复制代码
// Vue3:computed 自动追踪 products / keyword,自动缓存
const visible = computed(() =>
  products.value.filter(p => p.name.includes(keyword.value))
                .sort((a, b) => b.sales - a.sales)
)

核心区别再强调一次:

  • Vue computed 自动 收集依赖、自动缓存;
  • React useMemo 需要你手动写依赖数组,且它只是"性能优化提示",React 不保证一定缓存。

三、useCallback:缓存"函数"本身

这个 Vue 里几乎没有对应物,因为 Vue 的 setup 只运行一次,函数天然只创建一次。但 React 每次渲染都会重新创建函数,导致传给子组件的回调"每次都是新的",可能引发子组件不必要的重渲染。

jsx 复制代码
import { useCallback } from 'react'

function Parent({ userId }) {
  // ❌ 每次渲染都创建新函数,引用都不同
  const handleClick = () => doSomething(userId)

  // ✅ 只在 userId 变化时创建新函数,引用稳定
  const handleClick2 = useCallback(() => doSomething(userId), [userId])

  return <ExpensiveChild onClick={handleClick2} />
}

useCallback(fn, deps) 本质上等价于 useMemo(() => fn, deps)------一个缓存值,一个缓存函数。


四、React.memo:让子组件"props 没变就不重渲染"

useCallback 通常和 React.memo 搭配才有意义。默认情况下,父组件重渲染,所有子组件都会跟着重渲染(哪怕 props 没变)。这点和 Vue 不同------Vue 靠响应式做到精准更新,React 默认是"全家桶重渲染"。

React.memo 包裹的组件,会在 props 浅比较相等时跳过重渲染:

jsx 复制代码
import { memo } from 'react'

// 只有当 props 变化时才重渲染
const ExpensiveChild = memo(function ExpensiveChild({ onClick, data }) {
  console.log('子组件渲染')
  return <button onClick={onClick}>{data.name}</button>
})

三者的配合关系:

  • React.memo 让子组件在 props 不变时跳过渲染;
  • 但如果父组件每次传的 onClick 都是新函数,浅比较就不相等,memo 就失效了;
  • 所以要用 useCallback 稳定回调引用,用 useMemo 稳定对象/数组引用。
jsx 复制代码
function Parent() {
  const [count, setCount] = useState(0)
  const handleClick = useCallback(() => console.log('click'), [])  // 稳定引用
  const data = useMemo(() => ({ name: 'Tom' }), [])                // 稳定引用

  return (
    <>
      <button onClick={() => setCount(count + 1)}>{count}</button>
      <ExpensiveChild onClick={handleClick} data={data} />  {/* 不会跟着重渲染 */}
    </>
  )
}

五、React 19 的好消息:React Compiler

手动到处写 useMemo/useCallback/memo 确实繁琐------这正是 Vue 开发者觉得"React 好累"的主要原因。

好消息:React 19 引入了 React Compiler ,它能在编译期自动帮你做记忆化(memoization),让你几乎不用再手写 useMemo/useCallback。这让 React 的开发体验向 Vue 的"自动优化"靠拢了一大步。

启用 Compiler 后,你可以像写 Vue 一样直接写派生值和函数,编译器自动优化。目前它逐步普及中,但方向已经明确:未来手写 memo 的场景会越来越少

建议:新项目开启 React Compiler;在它未覆盖的关键性能路径,才手动用 useMemo/useCallback


六、何时该用、何时别用(决策表)

场景 该用吗
简单派生值(拼字符串、算总价) ❌ 直接写
大数组过滤/排序/复杂运算 useMemo
传给 React.memo 子组件的回调 useCallback
传给 React.memo 子组件的对象/数组 useMemo
普通事件处理函数(不往下传) ❌ 直接写
已启用 React 19 Compiler 多数情况交给编译器

黄金法则:先写正确的代码,遇到实测的性能问题再优化。 不要为了 memo 而 memo------过度记忆化本身也有成本,还会让代码变丑。


七、对照速查

需求 Vue3 React
简单派生值 computed 直接写变量
昂贵派生值 computed(自动缓存) useMemo(fn, deps)
稳定函数引用 无需(setup 只跑一次) useCallback(fn, deps)
跳过子组件重渲染 自动(响应式精准更新) React.memo + 稳定引用
依赖收集 自动 手动写依赖数组

小结

  • 普通派生值直接写 ,别滥用 useMemo------这是 Vue 开发者最常犯的过度优化。
  • useMemo 缓存昂贵计算结果;useCallback 缓存函数引用。
  • React 默认"父渲染子全渲染",用 React.memo + 稳定引用来精准跳过。
  • React 19 的 Compiler 正在把这些手动优化自动化,体验向 Vue 靠拢。
  • 原则:先正确,再按需优化。

最后一篇,我们从零用 Vite 搭一个 React 项目,写一个完整的 Todo 应用,把全系列知识点串起来实战。

相关推荐
用户2181697049301 小时前
Flutter(十)Container Center Align
前端
小p1 小时前
nextjs学习9: Next.js 渲染与缓存
前端·后端
乘风gg1 小时前
AI 时代,你的编程能力在第几层?我敢说,大多数人卡在第一层
前端·ai编程·claude
wear工程师1 小时前
React 的 key 为什么不能随手用 index:一插入列表,状态就串了
javascript·react.js
东风破_1 小时前
React Router v7 实战:用路由配置、懒加载与嵌套路由搭建一个完整的 SPA
前端
引山洪081 小时前
Babylon.js 8.x 中文文档整理——Node篇 (上)
前端·webgl
柒和远方1 小时前
V058:前端路由的第一性原理:从 hashchange 手写路由,到 React Router 的嵌套与懒加载
前端·javascript·react.js
计算机魔术师1 小时前
阿里云上线 One Key MCP 服务:兼容 Qoder、Codex 等,可一键调用多家 MCP 服务
前端
Darling噜啦啦1 小时前
React Router 全家桶实战:从路由懒加载到嵌套路由的 6 大核心玩法
前端·react.js