mcpField
R
react-performance

react-performance

github.com/claude-dev-suite开发4643 次安装4 天前更新
代码仓库

关于 react-performance

面向优化 React 应用的开发者性能技能,涵盖性能分析、记忆化、虚拟化、代码拆分和状态更新实践,并提供常见问题的排查建议。

主要功能

  1. 01使用 React API 进行记忆化
  2. 02长列表虚拟化
  3. 03路由与组件代码拆分
  4. 04优化状态更新
  5. 05性能分析与故障排查

适用场景

  1. 01减少组件的不必要重渲染
  2. 02改善大型列表的滚动体验
  3. 03通过代码拆分缩短初始加载
  4. 04诊断更新缓慢或包体积过大

MCP 相关

该技能引用 `mcp__documentation__fetch_docs` 获取 React 性能文档。

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/claude-dev-suite/claude-dev-suite --skill react-performance

安装后在 Claude Code 中输入/react-performance确认已加载。

附带文件advanced.md

详细介绍

React 性能优化

深入了解:使用 mcp__documentation__fetch_docs,指定 technology: react、topic: performance,获取 React 性能优化和性能分析技术的全面文档。

完整参考:请参阅 advanced.md,了解使用 React DevTools 进行性能分析、包优化、图像优化和 Web Workers。

记忆化

React.memo

当 props 未发生变化时,阻止组件重新渲染:

// Only re-renders when props change (shallow comparison)
const ExpensiveComponent = memo(function ExpensiveComponent({
  data,
  onItemClick,
}: Props) {
  return (
    <ul>
      {data.map(item => (
        <li key={item.id} onClick={() => onItemClick(item.id)}>
          {item.name}
        </li>
      ))}
    </ul>
  );
});

// With custom comparison
const OptimizedComponent = memo(
  function OptimizedComponent({ user }: Props) {
    return <div>{user.name}</div>;
  },
  (prevProps, nextProps) => prevProps.user.id === nextProps.user.id
);
何时使用 memo
// ✅ Good: Expensive component with stable parent
const ExpensiveList = memo(function ExpensiveList({ items }: { items: Item[] }) {
  return items.map(item => <ExpensiveItem key={item.id} item={item} />);
});

// ❌ Bad: Simple component, memo overhead not worth it
const SimpleText = memo(function SimpleText({ text }: { text: string }) {
  return <span>{text}</span>;
});

// ❌ Bad: Props always change anyway
function Parent() {
  // New object on every render - memo is useless!
  return <MemoizedChild data={{ value: 1 }} />;
}
useMemo

对开销较大的计算进行记忆化:

function ProductList({ products, filter }: Props) {
  const filteredProducts = useMemo(() => {
    return products
      .filter(p => p.category === filter.category)
      .filter(p => p.price >= filter.minPrice && p.price <= filter.maxPrice)
      .sort((a, b) => a.name.localeCompare(b.name));
  }, [products, filter.category, filter.minPrice, filter.maxPrice]);

  return (
    <ul>
      {filteredProducts.map(p => <ProductCard key={p.id} product={p} />)}
    </ul>
  );
}

// ❌ Don't overuse - simple operations don't need memoization
const total = useMemo(() => a + b, [a, b]); // Overkill!
useCallback

对函数进行记忆化,以避免子组件重新渲染:

function Parent() {
  const [count, setCount] = useState(0);

  // ✅ Stable reference for child components
  const handleClick = useCallback((id: string) => {
    console.log('Clicked:', id);
  }, []);

  // ✅ Dependencies that change function behavior
  const handleSubmit = useCallback((data: FormData) => {
    submitWithCount(data, count);
  }, [count]);

  return (
    <>
      <ChildComponent onClick={handleClick} />
      <Form onSubmit={handleSubmit} />
    </>
  );
}

虚拟化

对于大型列表,仅渲染可见项:

import { FixedSizeList } from 'react-window';

function VirtualList({ items }: { items: Item[] }) {
  const Row = ({ index, style }: { index: number; style: CSSProperties }) => (
    <div style={style} className="list-item">
      {items[index].name}
    </div>
  );

  return (
    <FixedSizeList
      height={400}
      width="100%"
      itemCount={items.length}
      itemSize={50}
    >
      {Row}
    </FixedSizeList>
  );
}
使用 TanStack Virtual
import { useVirtualizer } from '@tanstack/react-virtual';

function VirtualList({ items }: { items: Item[] }) {
  const parentRef = useRef<HTMLDivElement>(null);

  const virtualizer = useVirtualizer({
    count: items.length,
    getScrollElement: () => parentRef.current,
    estimateSize: () => 50,
    overscan: 5,
  });

  return (
    <div ref={parentRef} style={{ height: 400, overflow: 'auto' }}>
      <div style={{ height: virtualizer.getTotalSize(), position: 'relative' }}>
        {virtualizer.getVirtualItems().map((virtualItem) => (
          <div
            key={virtualItem.key}
            style={{
              position: 'absolute',
              top: 0,
              left: 0,
              width: '100%',
              height: virtualItem.size,
              transform: `translateY(${virtualItem.start}px)`,
            }}
          >
            {items[virtualItem.index].name}
          </div>
        ))}
      </div>
    </div>
  );
}

代码拆分

基于路由的拆分
import { lazy, Suspense } from 'react';
import { Routes, Route } from 'react-router-dom';

const Home = lazy(() => import('./pages/Home'));
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Settings = lazy(() => import('./pages/Settings'));

function App() {
  return (
    <Suspense fallback={<PageLoader />}>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/dashboard" element={<Dashboard />} />
        <Route path="/settings" element={<Settings />} />
      </Routes>
    </Suspense>
  );
}
基于组件的拆分
const HeavyChart = lazy(() => import('./HeavyChart'));

function Dashboard() {
  const [showChart, setShowChart] = useState(false);

  return (
    <div>
      <button onClick={() => setShowChart(true)}>Show Chart</button>

      {showChart && (
        <Suspense fallback={<ChartSkeleton />}>
          <HeavyChart data={data} />
        </Suspense>
      )}
    </div>
  );
}
预加载
const Dashboard = lazy(() => import('./Dashboard'));
const preloadDashboard = () => import('./Dashboard');

function NavLink() {
  return (
    <Link
      to="/dashboard"
      onMouseEnter={preloadDashboard}
      onFocus={preloadDashboard}
    >
      Dashboard
    </Link>
  );
}

避免不必要的重新渲染

稳定引用
// ❌ Bad: New object on every render
function Parent() {
  return <Child style={{ color: 'red' }} />;
}

// ✅ Good: Stable reference
const style = { color: 'red' };
function Parent() {
  return <Child style={style} />;
}

// ✅ Good: useMemo for dynamic values
function Parent({ color }) {
  const style = useMemo(() => ({ color }), [color]);
  return <Child style={style} />;
}
组件组合
// ❌ Bad: Entire component re-renders on count change
function App() {
  const [count, setCount] = useState(0);
  return (
    <div>
      <button onClick={() => setCount(c => c + 1)}>{count}</button>
      <ExpensiveComponent />  {/* Re-renders on every count change! */}
    </div>
  );
}

// ✅ Good: Move state down
function Counter() {
  const [count, setCount] = useState(0);
  return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
}

function App() {
  return (
    <div>
      <Counter />
      <ExpensiveComponent />  {/* Doesn't re-render! */}
    </div>
  );
}

// ✅ Good: Pass children as props
function Counter({ children }) {
  const [count, setCount] = useState(0);
  return (
    <div>
      <button onClick={() => setCount(c => c + 1)}>{count}</button>
      {children}  {/* Doesn't re-render! */}
    </div>
  );
}

状态管理优化

批量更新
// React 18+ automatically batches these
function handleClick() {
  setCount(c => c + 1);
  setFlag(f => !f);
  setText('updated');
  // Only ONE re-render!
}
派生状态
// ❌ Bad: Synchronized state
function Form() {
  const [items, setItems] = useState([]);
  const [total, setTotal] = useState(0);

  useEffect(() => {
    setTotal(items.reduce((sum, item) => sum + item.price, 0));
  }, [items]);  // Extra re-render!
}

// ✅ Good: Calculate during render
function Form() {
  const [items, setItems] = useState([]);
  const total = items.reduce((sum, item) => sum + item.price, 0);
}

// ✅ Good: useMemo for expensive calculations
function Form() {
  const [items, setItems] = useState([]);
  const total = useMemo(
    () => items.reduce((sum, item) => sum + item.price, 0),
    [items]
  );
}

常见问题

问题原因解决方案
初始渲染缓慢包体积过大代码拆分、延迟加载
更新缓慢重新渲染次数过多memo、useMemo、useCallback
滚动卡顿渲染了列表中的所有项虚拟化
内存泄漏未清理副作用正确的清理函数
布局抖动强制同步布局批量读取/写入 DOM

最佳实践

  • ✅ 优化前先进行性能分析
  • ✅ 使用 React DevTools Profiler
  • ✅ 对长列表(>100 项)进行虚拟化
  • ✅ 在路由层级进行代码拆分
  • ✅ 对开销较大的计算进行记忆化
  • ✅ 为对象/函数使用稳定引用
  • ❌ 不要过早进行过度优化
  • ❌ 不要给所有内容都包上 memo
  • ❌ 不要让繁重的 JavaScript 阻塞主线程

何时不应使用此技能

  • React 19 编译器优化 - 使用 react-19 技能了解编译器专属功能
  • React 基础开发 - 使用 react 技能进行常规组件开发
  • 测试 - 使用 react-testing 技能了解性能测试策略
  • 非性能相关的 React 问题 - 此技能专用于优化

反模式

反模式问题解决方案
过早优化浪费精力,代码复杂先进行性能分析,优化关键部分
给所有内容都包上 memo增加开销,没有收益只对开销较大的组件进行记忆化
对低成本计算使用 useMemo开销大于收益只对开销较大的操作进行记忆化
在 JSX 中使用内联对象/函数破坏子组件的记忆化提取为常量或使用 useCallback
未正确使用 key 属性整个列表重新渲染使用稳定且唯一的 ID
渲染整个列表滚动缓慢对超过 100 项的列表使用虚拟化
包体积大且未进行代码拆分初始加载缓慢按路由拆分

快速故障排查

问题可能原因修复方法
初始加载缓慢包体积过大拆分代码,延迟加载路由
滚动卡顿渲染了列表中的所有项使用 react-window 或 TanStack Virtual
频繁发生不必要的重新渲染props 的身份标识发生变化使用 useMemo/useCallback 对对象/函数进行记忆化
组件更新缓慢渲染过程中进行了繁重计算移至 useMemo 或 Web Worker
内存泄漏未清理副作用/订阅在 useEffect 中添加清理函数
JavaScript 负载过大未进行 tree-shaking只导入所需内容

参考文档

评论

支持 Markdown