原文地址:https://medium.com/@premchandak_11/react-rendering-secrets-7-proven-rules-to-stop-useless-re-renders-forever-bf34de524b63
原文作者: Prem Chandak
这篇文章的核心,是在 不需要的时候阻止 React 工作。读完之后,你将掌握 7 条具体规则,在 不靠 hack、不做过早优化 的前提下,让多余的 render 消失。
1. 先测量,再猜测
你看不到的东西,是没法优化的。第一步永远是搞清楚组件 什么时候 render。
React Profiler 正是为此而生 —— 它能精确告诉你 哪些组件发生了 render、耗时多久、为什么 render。
import { Profiler } from "react";
function logRender(id, phase, actualDuration) {
console.log(`${id} ${phase} took ${actualDuration.toFixed(2)}ms`);
}
<Profiler id="App" onRender={logRender}>
<App />
</Profiler>
每一次 commit,都会记录 render 的成本。你可以清楚看到 哪些组件在浪费时间 —— 而不是靠猜。
影响:
在优化之前先做 profiling,可以避免 80% 的无效优化。大多数“很慢”的应用,通常只是 2–3 个嘈杂组件在做不必要的更新。
2. React.memo 并不是银弹
React.memo 可以在 props 不变时跳过 re-render —— 但它 非常容易被误用。
const Button = React.memo(({ label }) => {
console.log("rendering button");
return <button>{label}</button>;
});
- 如果
label 是 primitive 类型,没问题
- 如果它来自对象计算或内联函数 —— 依然会 re-render
<Button label={computeLabel()} /> // 每次 render 都是新值
修正方式:
const label = useMemo(() => computeLabel(), [data]);
<Button label={label} />;
现在,React 能看到 稳定的 props 引用 了。
规则:
先 memoize props,再 memoize 组件。

3. useCallback:稳定引用的生命线
内联函数 是 React 中最隐蔽的 re-render 触发器。
每一次 render,都会创建一个新的函数 ——React 认为 props 变了 —— 子组件立刻 re-render。
// Bad
<Child onClick={() => setCount(count + 1)} />
// Good
const handleClick = useCallback(() => setCount(c => c + 1), []);
<Child onClick={handleClick} />
示意:
[Parent Render]
|
|-- [onClick: new fn()] -> Child re-renders
|
|-- [onClick: stable fn()] -> Child stays quiet
影响: 在复杂组件树中,子组件 render 次数最多可减少 70%。
4. State 的位置,比你想象中更重要
state 放得越靠上,引发的 render 链条就越长。
// Parent.js
function Parent() {
const [value, setValue] = useState(0);
return (
<>
<ChildA value={value} />
<ChildB />
</>
);
}
当 setValue 执行时,两个子组件都会 re-render ——即使 ChildB 根本没用到 value。
更好的做法:把 state 下移
function ChildA() {
const [value, setValue] = useState(0);
...
}
示意图:
Before:
Parent (state)
├─ ChildA (depends)
└─ ChildB (unrelated) re-renders
After:
Parent
├─ ChildA (local state) isolated
└─ ChildB (unaffected)
关键结论:
state 离使用它的地方越近,浪费的 render 就越少。
5. Context:强大,但危险
React Context 只要 value 发生变化,就会触发所有 consumer re-render ——无论变化有多小。
const UserContext = createContext();
<UserContext.Provider value={{name, age }}>
<Profile />
</UserContext.Provider>
哪怕只是 age 变了,Profile 依然会因为 name 而 re-render。
修正方案:拆分 Context
<UserNameContext.Provider value={name}>
<UserAgeContext.Provider value={age}>
<Profile />
</UserAgeContext.Provider>
</UserNameContext.Provider>
或者使用 自定义 selector 模式。
迷你基准测试(Chrome Profiler):
Pattern Avg Re-render Time
Single context 7.2 ms
Split contexts 3.1 ms
Selector hook 2.8 ms
权衡:
Context 越多,样板代码越多,但 隔离性更好。
6. Derived State:隐藏的 re-render 循环
如果一个值可以推导出来,就不要把它存进 state。
// Bad:数据重复
const [filtered, setFiltered] = useState([]);
useEffect(() => {
setFiltered(list.filter(isActive));
}, [list]);
list 每变一次,就会:
- render
- 执行 useEffect
- setState
- 再 render 一次
更好的方式:
const filtered = useMemo(() => list.filter(isActive), [list]);
没有额外 render,没有同步问题,只是派生数据。
流程对比:
State -> Re-render -> useEffect -> setState -> Re-render again
State -> Re-render -> useMemo -> ✅ derived on demand
影响:
每次依赖变更,直接减少一次完整 render —— 干净、安全。
7. 避免列表中的 re-render 链条
列表是 React 的 阿喀琉斯之踵。一个错误的 key,就能让全部重新 render。
{items.map((item, i) => (
<Row key={i} data={item} />
))}
当顺序变化时,所有 key 都变了 —— 所有行都会 re-render。
始终使用稳定、唯一的 key:
<Row key={item.id} data={item} />
同时,对列表项本身进行 memo:
const Row = React.memo(({ data }) => {
return <div>{data.name}</div>;
});
对比:
Before:
[1,2,3] -> re-order -> [3,2,1]
Keys: 0,1,2 全部替换
After:
Keys: 基于 ID 只 re-render 移动的元素
影响:
在大型表格中,每一次用户操作都能 避免数百次无效 render。
掌握这七条规则,你就能系统性地审视你的 React 应用,从源头上杜绝无谓的性能损耗。希望这些实践能帮助你在 云栈社区 的交流中,分享更高效的前端开发经验。