React组件渲染太慢?这几个优化方法你得知道

最近在做一个后台管理项目,页面里有个表格组件,数据一多,滑动都卡。刚开始以为是接口慢,结果发现接口早就回来了,页面还是半天没反应——问题出在 React 组件渲染太慢上。

先看看是不是不必要的重渲染

React 默认在父组件更新时,子组件也会跟着重新 render。如果某个子组件其实数据没变,也跟着跑一遍渲染逻辑,那性能肯定扛不住。比如你有个头像组件,父组件列表刷新,它也跟着 rerender,其实头像根本没变。

这时候可以用 React.memo 包一层:

const Avatar = React.memo(({ userId }) => {
  return <img src={`/api/avatar/${userId}`} />;
});

这样只有当 userId 变了,才会重新渲染。简单加个 memo,页面流畅多了。

大量列表?别忘了 key 要用对

列表渲染卡,有时候不是因为数据多,而是 key 没设好。比如你用数组索引当 key:

{items.map((item, index) =>
  <div key={index}>{item.name}</div>
)}

一旦数组顺序变了,或者中间插入一项,后面所有 item 的 index 都变了,React 就会认为每个元素都变了,全部重新创建 DOM。换成唯一 id 当 key 才靠谱:

{items.map(item =>
  <div key={item.id}>{item.name}</div>
)}

复杂计算放 useCallback 和 useMemo

有些组件里要算个总数、过滤列表,逻辑一复杂,每次 render 都重新算一遍,自然就慢。比如:

const filteredList = expensiveFilter(items, status);

可以改成:

const filteredList = useMemo(() =>
  expensiveFilter(items, status),
  [items, status]
);

这样只有 itemsstatus 变了才重新计算。同理,函数也可以用 useCallback 缓存,避免传给子组件时触发无谓更新。

大组件拆小点,按需加载

一个页面全塞在一个组件里,哪怕只改了一个按钮,整个页面都 rerender。把页面拆成多个独立模块,比如搜索框、筛选栏、表格、分页器,各自管理自己的状态,互不干扰。

更进一步,用 React.lazy + Suspense 做动态加载:

const LazyReportChart = React.lazy(() =>
  import('./ReportChart')
);

// 使用时
<Suspense fallback="加载中...">
  <LazyReportChart />
</Suspense>

首屏只加载必要的部分,其他等需要时再加载,页面启动更快,整体体验也更顺。

实在不行,节流一下 setState

有些场景比如拖拽、实时搜索,state 更新太频繁,React 来不及处理,就会堆着一堆任务卡住。可以在外面加个节流:

import { throttle } from 'lodash';

const handleScroll = throttle((e) => {
  setScrollTop(e.target.scrollTop);
}, 100);

不让 state 更新得太猛,给浏览器喘口气的时间。

这些问题其实在日常开发里挺常见的。别一上来就怪 React 慢,很多时候是写法可以更聪明一点。改几个地方,页面立马从“卡成PPT”变成“丝般顺滑”。