我被 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 和交流。

相关推荐
HUMHSX22 分钟前
Vue 项目启动全流程解析:从入口文件到全局指令注册与页面渲染
前端·javascript·vue.js
有颜有货34 分钟前
PMC生产排产的4种算法,一次讲清
java·服务器·前端
小虎牙00736 分钟前
Android kotlin图片库Coil源码详解
android·前端
随风一样自由1 小时前
【前端领域】前端开发核心应用场景与落地实践
前端·前端框架
an317421 小时前
弹窗数据流设计的两种高阶架构实践
前端·vue.js·架构
谢尔登1 小时前
【React】 状态管理方案
前端·react.js·前端框架
用户2136610035722 小时前
Vue商品详情与放大镜组件
前端·javascript
半个落月2 小时前
从Tapas小Demo理清localStorage、事件与this
前端·javascript
李明卫杭州2 小时前
Vue2 中 v-model 处理不同数据结构的技巧
前端·javascript·vue.js
李明卫杭州2 小时前
使用 computed 处理 v-model 复杂数据结构
前端·javascript·vue.js