找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

2499

积分

0

好友

359

主题
发表于 4 小时前 | 查看: 2| 回复: 0

原文地址: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 组件。

React.memo优化前后组件依赖关系对比

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 每变一次,就会:

  1. render
  2. 执行 useEffect
  3. setState
  4. 再 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 应用,从源头上杜绝无谓的性能损耗。希望这些实践能帮助你在 云栈社区 的交流中,分享更高效的前端开发经验。




上一篇:Meta DrP 大规模根因分析平台:自动化故障排查如何将MTTR降低80%
下一篇:Claude Code插件规划工作流,重现Manus的20亿美元估值秘诀
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-1-16 18:15 , Processed in 0.280188 second(s), 39 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表