最近在做一个后台管理项目,页面里有个表格组件,数据一多,滑动都卡。刚开始以为是接口慢,结果发现接口早就回来了,页面还是半天没反应——问题出在 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]
);
这样只有 items 或 status 变了才重新计算。同理,函数也可以用 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”变成“丝般顺滑”。