React性能优化Skill react-performance

这项技能涵盖了 React 应用性能优化的多种模式和实践,包括记忆化、虚拟化、选择器优化、避免不必要渲染等,旨在提升 React 应用的性能和用户体验。

前端开发 0 次安装 0 次浏览 更新于 3/3/2026

name: react-performance description: React 网站应用的性能优化。在优化渲染、实现虚拟化、记忆组件或调试性能问题时使用。

React 性能(Web)

问题陈述

React 性能问题通常源于不必要的重新渲染、未优化的列表和主线程上的昂贵计算。理解 React 的渲染行为是构建高性能应用的关键。


模式:记忆化

useMemo - 昂贵计算

// ✅ 正确:记忆化昂贵计算
const sortedAndFilteredItems = useMemo(() => {
  return items
    .filter(item => item.active)
    .sort((a, b) => b.score - a.score)
    .slice(0, 100);
}, [items]);

// ❌ 错误:每次渲染重新计算
const sortedAndFilteredItems = items
  .filter(item => item.active)
  .sort((a, b) => b.score - a.score);

// ❌ 错误:记忆化简单访问(开销 > 好处)
const userName = useMemo(() => user.name, [user.name]);

何时使用 useMemo:

  • 数组转换(filter, sort, map 链)
  • 传递给记忆化子组件的对象创建
  • O(n) 或更高复杂度的计算

useCallback - 稳定的函数引用

// ✅ 正确:子组件属性的稳定回调
const handleClick = useCallback((id: string) => {
  setSelectedId(id);
}, []);

// 传递给记忆化子组件
<MemoizedItem onClick={handleClick} />

// ❌ 错误:useCallback 与不稳定依赖
const handleClick = useCallback((id: string) => {
  doSomething(unstableObject); // unstableObject 每次渲染都变化
}, [unstableObject]); // 适得其反

何时使用 useCallback:

  • 传递给记忆化子组件的回调
  • 依赖数组中的回调
  • 会导致子组件重新渲染的事件处理程序

模式:React.memo

// 包装接收稳定属性的组件
const ItemCard = memo(function ItemCard({
  item,
  onSelect
}: Props) {
  return (
    <div onClick={() => onSelect(item.id)}>
      <h3>{item.name}</h3>
      <p>{item.price}</p>
    </div>
  );
});

// 复杂属性的自定义比较
const ItemCard = memo(
  function ItemCard({ item, onSelect }: Props) {
    // ...
  },
  (prevProps, nextProps) => {
    // 返回 true 如果属性相等(跳过重新渲染)
    return (
      prevProps.item.id === nextProps.item.id &&
      prevProps.item.price === nextProps.item.price
    );
  }
);

何时使用 React.memo:

  • 列表项组件
  • 接收稳定原始属性的组件
  • 频繁渲染但很少变化的组件

何时不使用:

  • 总是接收新属性的组件
  • 简单组件(开销 > 好处)
  • 根级页面

模式:列表虚拟化

对于长列表,使用 react-window 或 react-virtualized 只渲染可见项。

import { FixedSizeList } from 'react-window';

function VirtualizedList({ items }: { items: Item[] }) {
  const Row = ({ index, style }: { index: number; style: React.CSSProperties }) => (
    <div style={style}>
      <ItemCard item={items[index]} />
    </div>
  );

  return (
    <FixedSizeList
      height={600}
      width="100%"
      itemCount={items.length}
      itemSize={80}
    >
      {Row}
    </FixedSizeList>
  );
}

// 可变高度项
import { VariableSizeList } from 'react-window';

function VariableList({ items }: { items: Item[] }) {
  const getItemSize = (index: number) => {
    return items[index].expanded ? 200 : 80;
  };

  return (
    <VariableSizeList
      height={600}
      width="100%"
      itemCount={items.length}
      itemSize={getItemSize}
    >
      {Row}
    </VariableSizeList>
  );
}

何时虚拟化:

  • 有 100+ 项的列表
  • 复杂的项组件
  • 有许多子项的可滚动容器

模式:Zustand 选择器优化

问题: 选择整个存储会导致任何状态变化时重新渲染。

// ❌ 错误:在任何存储变化时重新渲染
const store = useAppStore();
// 或
const { items, loading, filters, ... } = useAppStore();

// ✅ 正确:仅在选定值变化时重新渲染
const items = useAppStore((s) => s.items);
const loading = useAppStore((s) => s.loading);

// ✅ 正确:多个值与浅比较
import { useShallow } from 'zustand/react/shallow';

const { items, loading } = useAppStore(
  useShallow((s) => ({
    items: s.items,
    loading: s.loading
  }))
);

模式:避免重新渲染

对象/数组稳定性

// ❌ 错误:每次渲染新对象
<ChildComponent style={{ padding: 10 }} />
<ChildComponent config={{ enabled: true }} />

// ✅ 正确:稳定引用
const style = useMemo(() => ({ padding: 10 }), []);
const config = useMemo(() => ({ enabled: true }), []);

<ChildComponent style={style} />
<ChildComponent config={config} />

// ✅ 正确:或在组件外定义
const style = { padding: 10 };

function Parent() {
  return <ChildComponent style={style} />;
}

子组件稳定性

// ❌ 错误:内联函数每次渲染创建新元素
<Parent>
  {() => <Child />}
</Parent>

// ✅ 正确:稳定元素
const child = useMemo(() => <Child />, [deps]);
<Parent>{child}</Parent>

模式:代码拆分

import { lazy, Suspense } from 'react';

// 懒加载组件
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Settings = lazy(() => import('./pages/Settings'));

function App() {
  return (
    <Suspense fallback={<Loading />}>
      <Routes>
        <Route path="/dashboard" element={<Dashboard />} />
        <Route path="/settings" element={<Settings />} />
      </Routes>
    </Suspense>
  );
}

// 命名导出
const Dashboard = lazy(() =>
  import('./pages/Dashboard').then(module => ({
    default: module.Dashboard
  }))
);

模式:防抖和节流

import { useMemo } from 'react';
import { debounce, throttle } from 'lodash-es';

// 防抖 - 等待用户停止输入
function SearchInput({ onSearch }: { onSearch: (query: string) => void }) {
  const debouncedSearch = useMemo(
    () => debounce(onSearch, 300),
    [onSearch]
  );

  return (
    <input
      type="text"
      onChange={(e) => debouncedSearch(e.target.value)}
    />
  );
}

// 节流 - 限制函数运行频率
function InfiniteScroll({ onLoadMore }: { onLoadMore: () => void }) {
  const throttledLoad = useMemo(
    () => throttle(onLoadMore, 1000),
    [onLoadMore]
  );

  useEffect(() => {
    const handleScroll = () => {
      if (nearBottom()) {
        throttledLoad();
      }
    };

    window.addEventListener('scroll', handleScroll);
    return () => window.removeEventListener('scroll', handleScroll);
  }, [throttledLoad]);

  return <div>...</div>;
}

模式:图片优化

// 懒加载图片
<img
  src={imageUrl}
  loading="lazy"
  alt="Description"
/>

// 使用 intersection observer 获得更多控制
function LazyImage({ src, alt }: { src: string; alt: string }) {
  const [isVisible, setIsVisible] = useState(false);
  const imgRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    const observer = new IntersectionObserver(
      ([entry]) => {
        if (entry.isIntersecting) {
          setIsVisible(true);
          observer.disconnect();
        }
      },
      { rootMargin: '100px' }
    );

    if (imgRef.current) {
      observer.observe(imgRef.current);
    }

    return () => observer.disconnect();
  }, []);

  return (
    <div ref={imgRef}>
      {isVisible ? (
        <img src={src} alt={alt} />
      ) : (
        <div className="placeholder" />
      )}
    </div>
  );
}

// 如果使用 Next.js 的 Next.js 图像组件
import Image from 'next/image';

<Image
  src={imageUrl}
  alt="Description"
  width={400}
  height={300}
  placeholder="blur"
  blurDataURL={blurHash}
/>

模式:Web Workers 用于繁重计算

// worker.ts
self.onmessage = (e: MessageEvent<{ data: number[] }>) => {
  const result = heavyComputation(e.data.data);
  self.postMessage(result);
};

// 组件
function DataProcessor({ data }: { data: number[] }) {
  const [result, setResult] = useState(null);

  useEffect(() => {
    const worker = new Worker(new URL('./worker.ts', import.meta.url));

    worker.onmessage = (e) => {
      setResult(e.data);
    };

    worker.postMessage({ data });

    return () => worker.terminate();
  }, [data]);

  return result ? <Results data={result} /> : <Loading />;
}

模式:检测重新渲染

React DevTools Profiler

  1. 打开 React DevTools
  2. 转到 Profiler 标签
  3. 点击记录,交互,停止
  4. 查看 “Flamegraph” 以获取渲染时间
  5. 查找不必要渲染的组件

why-did-you-render

// 开发环境设置
import React from 'react';

if (process.env.NODE_ENV === 'development') {
  const whyDidYouRender = require('@welldone-software/why-did-you-render');
  whyDidYouRender(React, {
    trackAllPureComponents: true,
  });
}

// 标记特定组件进行跟踪
ItemCard.whyDidYouRender = true;

控制台日志

// 快速检查重新渲染
function ItemCard({ item }: Props) {
  console.log('ItemCard render:', item.id);
  // ...
}

性能清单

发布前:

  • [ ] 长列表已虚拟化
  • [ ] 列表项已使用 React.memo 记忆化
  • [ ] 传递给项的回调使用 useCallback
  • [ ] Zustand 选择器是特定的(不是整个存储)
  • [ ] 图片使用懒加载
  • [ ] 重路由已代码拆分
  • [ ] 没有内联对象/函数属性传递给记忆化子组件
  • [ ] Profiler 显示没有不必要的重新渲染

常见问题

问题 解决方案
列表滚动延迟 虚拟化列表,记忆化项
组件重新渲染太频繁 检查选择器特定性,记忆化属性
初始渲染缓慢 代码拆分,减少包大小
内存增长 检查事件侦听器清理,状态累积
交互时 UI 冻结 将计算移动到 Web Worker 或延迟

与其他技能的关系

  • react-zustand-patterns: 选择器优化模式
  • react-async-patterns: 适当的异步处理防止重新渲染循环