首页 / 文章 / 超越手动实现的 memo 的 React 性能:结构与调度机制

超越手动实现的 memo 的 React 性能:结构与调度机制

编译器时代的建议:将状态集中管理,区分紧急任务,减少JS代码输出,再对检测到的高频调用点进行缓存。

803 词

多年来,关于 React 性能优化的建议一直都是将所有内容用 memo API 包装:

const value = useMemo(() => expensiveCalculation(data), [data]);
const handleClick = useCallback(() => {
  doSomething(id);
}, [id]);export default React.memo(Component);
function ProductList({ products, onSelect }) {
  const processed = useMemo(
    () => processProducts(products),
    [products]
  );
  const handleSelect = useCallback(
    (id) => onSelect(id),
    [onSelect]
  );  return (
    <List
      products={processed}
      onSelect={handleSelect}
    />
  );
}
App
 │
 ├── Header
 ├── Sidebar
 ├── Search
 ├── ProductList
 └── Footer
function App() {
  const [query, setQuery] = useState("");
  // large component tree
}
App
 │
 ├── Header
 ├── Sidebar
 ├── Search
 │    └── query state
 ├── ProductList
 └── Footer
const [query, setQuery] = useState("");
const [isPending, startTransition] = useTransition();
function handleChange(event) {
  const value = event.target.value;  setQuery(value);  startTransition(() => {
    setSearchResults(filterProducts(value));
  });
}
User input
    │
    ├── urgent ───────► keep UI responsive
    │
    └── non-urgent ───► transition
const AnalyticsDashboard = lazy(
  () => import("./AnalyticsDashboard")
);
Initial JavaScript
       │
       ▼
┌─────────────────────┐
│ Everything          │
│ Dashboard           │
│ Analytics           │
│ Editor              │
│ Admin               │
└─────────────────────┘
Initial load
    │
    ├── Core UI
    │
    └── Later
          ├── Analytics
          ├── Editor
          └── Admin
"This component renders often."
        │
        ▼
Add useMemo
"This component renders often."
        │
        ▼
Why?
        │
 ┌──────┼───────────┐
 ▼      ▼           ▼
State  Expensive   Large
flow   work        subtree
 │      │           │
 ▼      ▼           ▼
Colocate  Optimize  Restructure
1. Measure
      ↓
2. Find the expensive work
      ↓
3. Fix component architecture
      ↓
4. Reduce unnecessary JavaScript
      ↓
5. Prioritize updates correctly
      ↓
6. Let React Compiler handle memoization
      ↓
7. Manually optimize only when evidence says you should

仅靠记忆化并不能解决应用运行缓慢的问题。即使树结构已经完全实现记忆化,仍可能因状态过大、紧急更新却执行非紧急任务,或是主线程上存在大量 JavaScript 代码而导致性能问题。

React 编译器改变了这一默认做法

编译器自动处理了许多记忆化相关的工作,这样人们就可以将精力集中在代码结构和调度上,而无需再到处手动添加 useMemo。

第一步:将状态放在更接近使用它的地方

树中的高状态会重新渲染整个子树。将状态与需要它的叶子节点放在一起;只有在共享时有必要才进行提升操作。

第二:不要让紧急更新去处理非紧急任务

保持输入和动画的响应性。通过过渡效果或延迟值来推迟非紧急任务,避免按键操作被耗时的过滤器阻塞。

第三:审视你发布的 JavaScript 代码

在 React 的协调器发挥作用之前,庞大的同步循环、耗时的格式化函数以及无限制的列表就已经占据了主导地位。应对 JavaScript 代码进行性能分析,而不仅仅是依赖 React DevTools 的提示。

那么何时应该使用 useMemo 呢?

在子组件因属性变化而失效时保持引用稳定性,以及在进行真正耗时的纯计算之后——在经过测量之后——它仍然很有用。但它并非适用于所有值的默认解决方案。

React 的新性能思维方式

结构 → 调度 → 减少 JavaScript 的使用量 → 再对高频访问区域进行微缓存。编译器辅助的缓存功能只是辅助工具,不能替代良好的设计。

在代码提交中需设定性能预算:主要表单的交互到下一次重绘的时间,以及已知性能较差路径的 React 性能分析快照。

列表应在对每一行进行缓存之前先实现虚拟化处理。即便对 10,000 行数据进行了缓存,依然无法提升性能。

在代码提交中需设定性能预算:主要表单的交互到下一次重绘的时间,以及已知性能较差路径的 React 性能分析快照。

列表应在对每一行进行缓存之前先实现虚拟化处理。即便对 10,000 行数据进行了缓存,依然无法提升性能。

在代码提交中需设定性能预算:主要表单的交互到下一次重绘的时间,以及已知性能较差路径的 React 性能分析快照。

列表应在对每一行进行缓存之前先实现虚拟化处理。即便对 10,000 行数据进行了缓存,依然无法提升性能。

为拉取请求设定性能预算:主表单需测量到下一次重绘的交互时间,同时针对已知性能较差的路由生成 React Profiler 的快照。

列表应在对每一行进行缓存之前先实现虚拟化处理。即便在包含1万行的页面加载时使用缓存,性能依然会不佳。

为拉取请求设定性能预算:主表单需测量到下一次重绘的交互时间,同时针对已知性能较差的路由生成 React Profiler 的快照。

列表应在对每一行进行缓存之前先实现虚拟化处理。即便在包含1万行的页面加载时使用缓存,性能依然会不佳。

为拉取请求设定性能预算:主表单需测量到下一次重绘的交互时间,同时针对已知性能较差的路由生成 React Profiler 的快照。

列表应在对每一行进行缓存之前先实现虚拟化处理。即便在包含1万行的页面加载时使用缓存,性能依然会不佳。