React 卡顿,十有八九不是框架的锅,是组件在做白工——一个 state 变了,下面一大片莫名其妙跟着重渲染。先搞明白它什么时候会重渲染,再动手,不然全是瞎忙。
会触发重渲染的情况无非三种:自己的 state / props 变了、父组件渲染了、用到的 context 值变了。第二种最容易被忽略——父组件一渲染,哪怕子组件 props 一个字没改,子组件函数照样再跑一遍。
memo 能挡,但有坑
给子组件套个 React.memo,props 没变它就跳过:
const Item = React.memo(function Item({ title }: { title: string }) {
return <li>{title}</li>;
});
可你要是这么传 props,memo 基本白加:
// onClick 每次都是新函数,浅比较永远不相等,照样重渲染
<Item title={t} onClick={() => select(t)} />
每次父组件渲染,那个箭头函数都是个全新引用,memo 一比发现 props「变了」,于是该渲染还是渲染。
所以才需要 useCallback / useMemo
const handleSelect = useCallback((t: string) => select(t), [select]);
const sorted = useMemo(() => list.slice().sort(), [list]);
把引用稳住,配合 memo 才有意义。但也别上头——给个加减法、一个短字符串包 useMemo,省下的计算还不够它自己开销大,纯属帮倒忙。
几个顺手能做的
- state 往下放:只影响局部的状态别堆在顶层。输入框的受控 state 塞进子组件里,父组件就不会每敲一个字母就把整棵树重渲染一遍。
- key 别拿 index 凑:数组下标当 key,一插入删除就错位,bug 和性能问题一起来,老实用业务 id。
最后提一嘴:动手之前先开 React DevTools 的 Profiler 录一段,看清楚到底是谁在重渲染。别凭感觉满屏 memo,加一堆结果该卡还卡。
