我被 React 性能问题逼疯了,直到学会这 4 个优化技巧

一个搜索框,卡到同事过来问我"你电脑是不是该换了"------我默默打开 Performance 面板,发现组件渲染了 37 次。


😅 第一次被性能问题暴击

事情要从一个搜索页面说起。

需求很简单:一个输入框,一个列表,一个分页。

我花了半天写完,自信满满地点开测试------输入"react",页面卡了 2 秒才出结果。

我当时没在意,以为是接口慢。

直到我用 React DevTools 的 Profiler 看了一眼,才发现一个惊悚的事实:

每次输入,整个列表 + 分页组件 + 甚至页脚的版权信息,全部重新渲染了一遍。

我的搜索框,每敲一个字母,全页 20 多个组件就集体"上班"一次。


🤯 罪魁祸首:父组件更新 = 子组件全量重渲染

先看一段简化后的代码,你是不是也这么写过:

jsx 复制代码
function SearchPage() {
  const [keyword, setKeyword] = useState("")
  const [list, setList] = useState([])

  return (
    <div>
      <SearchInput value={keyword} onChange={setKeyword} />
      <UserList data={list} />
      <Pagination total={list.length} />
    </div>
  )
}

看起来没毛病对吧?

但真相是:只要 keyword 变一次,UserListPagination 也会跟着重新渲染。

为什么?

因为 setKeyword 触发 SearchPage 重新执行,SearchPage 返回的新 JSX 里,<UserList><Pagination>全新的虚拟 DOM,React 的 diff 算法发现"旧的引用 ≠ 新的引用"------于是全部重渲。

这就是 React 新手最容易踩的坑:状态变了,整个函数组件重新跑,子组件全量跟着跑。


🧩 优化一:React.memo --- 最简单的无脑缓存

对于纯展示组件,用 React.memo 包一层就够了:

jsx 复制代码
const UserList = React.memo(function UserList({ data }) {
  console.log("UserList 渲染了 🙄")
  return (
    <ul>
      {data.map(item => (
        <li key={item.id}>{item.name}</li>
      ))}
    </ul>
  )
})

原理很简单:React.memo 会对 props浅比较 ,如果 props 没变,直接复用上一次的渲染结果。

加上这一层后,我的搜索框再打字,UserList 终于不会跟着刷了。

踩坑提醒React.memo 只做浅比较。如果你传了对象或数组作为 props,每次父组件重新执行时都会创建新引用,memo 就失效了。这时候需要配合 useMemo


🧩 优化二:useMemo --- 缓存计算结果

再看一个真实场景:列表数据需要做复杂过滤和排序。

jsx 复制代码
function UserList({ data, filterText }) {
  // 没优化:每次渲染都重新过滤 + 排序
  const filtered = data
    .filter(item => item.name.includes(filterText))
    .sort((a, b) => a.age - b.age)

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

如果 data 有 10000 条,每次父组件更新都重新过滤一遍------卡死。

useMemo 缓存:

jsx 复制代码
const filtered = useMemo(
  () => data
    .filter(item => item.name.includes(filterText))
    .sort((a, b) => a.age - b.age),
  [data, filterText] // 只有这两个变了才重新计算
)

适用场景:大数据量过滤、排序、复杂数学计算。简单的加法就没必要了------useMemo 本身也有开销。


🧩 优化三:useCallback --- 缓存函数引用

这个坑我踩得最冤:

jsx 复制代码
function SearchPage() {
  const handleSearch = (value) => {
    console.log("搜索:", value)
  }

  return <SearchInput onSearch={handleSearch} />
}

const SearchInput = React.memo(function SearchInput({ onSearch }) {
  // onSearch 每次都是新函数 → React.memo 白写了
})

handleSearch 是每次渲染都在函数组件里重新定义的,所以它的引用每次都不同。

React.memo 比较 props 时发现 onSearch 变了------等于没优化。

修复方案:useCallback

jsx 复制代码
const handleSearch = useCallback((value) => {
  console.log("搜索:", value)
}, []) // 依赖数组为空,引用永远不变

记住useCallback 单独用没意义,它必须配合 React.memo 才有价值。


🧩 优化四:Key 选不对,列表整个重渲

很多人写列表渲染时图省事:

jsx 复制代码
// ❌ 错误示范
{items.map((item, index) => <li key={index}>{item.name}</li>)}

index 作为 key 的后果:

  • 列表顺序变了 → React 认错元素 → 复用错误的 DOM
  • 列表增删了 → 所有元素 key 都变了 → 整个列表销毁重建
  • 如果有输入框或选中状态 → 状态错乱

正确的做法

jsx 复制代码
// ✅ 用唯一 id
{items.map(item => <li key={item.id}>{item.name}</li>)}

如果接口没给 id,用 uuid 或者自己维护一个自增 ID。总之------永远不要用 index


🔥 终极方案:虚拟列表

如果你的列表动辄上万条,前面的优化都治标不治本。

正确的思路是:只渲染用户能看到的那几行。

react-window 实现:

bash 复制代码
npm install react-window
jsx 复制代码
import { FixedSizeList } from "react-window"

function VirtualUserList({ data }) {
  return (
    <FixedSizeList
      height={600}
      itemCount={data.length}
      itemSize={50}
      width="100%"
    >
      {({ index, style }) => (
        <div style={style}>{data[index].name}</div>
      )}
    </FixedSizeList>
  )
}

效果:10000 条数据,只渲染屏幕上的 12 行,滚动时动态替换。

真实案例:我在一个后台管理系统中,把用户列表从渲染 5000 个 DOM 节点降到了 30 个,页面加载时间从 3.2s 降到了 0.4s。


📌 总结一下这 4 个优化技巧

技巧 解决什么问题 使用时机
React.memo 子组件不必要的重复渲染 纯展示组件,props 基本不变
useMemo 复杂计算重复执行 大数据量过滤、排序
useCallback 函数引用变化破坏 memo 配合 React.memo 使用
正确 key 列表渲染错乱 所有列表渲染
虚拟列表 海量数据 DOM 过多 上千条的长列表

我给团队定了一条规矩:所有列表页面,默认上 react-window + React.memo

不是因为一定要优化到极致,而是养成习惯后,你永远不会被性能问题半夜叫醒。


🗣 最后

你有没有遇到过"明明代码没写错,但页面就是卡"的经历?

欢迎在评论区分享你的踩坑故事------你用什么手段查出来的?用了什么方案解决?


📌 完整源码

本文所有示例代码都在 github.com/example/rea...,欢迎 star 和交流。

相关推荐
窗边的anini2 小时前
那个因为 vibecoding 差点搞砸约会的女孩,被 TRAE SOLO 救了
前端·人工智能·程序员
用户713874229002 小时前
OAuth 2.0 client_id深度解析:从规范到安全实践
前端
ZC跨境爬虫2 小时前
跟着 MDN 学CSS day_8:(盒模型完全解)
前端·javascript·css·ui·交互
Cache技术分享2 小时前
415. Java 文件操作基础 - 精准读取压缩诗集:从二进制文件中高效提取指定十四行诗
前端·后端
光影少年2 小时前
react自定义Hook 写法、规则(只能在组件/自定义Hook内调用)
前端·react.js·掘金·金石计划
风骏时光牛马2 小时前
C语言核心高频问题与代码实战梳理
前端
葬送的代码人生2 小时前
别再「Ctrl+C/V」了!Git 开发必备技能,10 分钟告别单机码农
前端·github·代码规范
xuankuxiaoyao2 小时前
vue.js 设计与开发 ---路由
前端·javascript·vue.js
ZC跨境爬虫3 小时前
跟着 MDN 学CSS day_6:(伪类和伪元素详解)
前端·javascript·css·数据库·ui·html