React性能优化Skill react-performance

这个技能用于优化React应用程序的性能,通过组件记忆化、懒加载代码分割、虚拟滚动列表、性能监控等技术,提升应用响应速度、减少加载时间并改善用户体验。关键词:React性能优化、记忆化、懒加载、虚拟滚动、性能监控、代码分割、useMemo、useCallback、React.memo。

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

名称:react-performance 用户可调用:false 描述:用于React性能优化,包括记忆化、懒加载和虚拟化。用于优化React应用程序。 允许工具:

  • Bash
  • 读取

React 性能优化

掌握React性能优化,用于构建高性能、可扩展的React应用程序,采用行业最佳实践。

React.memo 和组件记忆化

React.memo通过记忆化组件输出来防止不必要的重新渲染:

import { memo } from 'react';

interface Props {
  name: string;
  onClick: () => void;
}

// 基本记忆化
const ExpensiveComponent = memo(
  function ExpensiveComponent({ name, onClick }: Props) {
  console.log('渲染 ExpensiveComponent');
  return <button onClick={onClick}>{name}</button>;
});

// 自定义比较函数
const CustomMemo = memo(
  function Component({ user }: { user: User }) {
    return <div>{user.name}</div>;
  },
  (prevProps, nextProps) => {
    // 如果传递nextProps会返回与prevProps相同的结果,则返回true
    return prevProps.user.id === nextProps.user.id;
  }
);

// 何时使用自定义比较
const ProductCard = memo(
  function ProductCard({ product }: { product: Product }) {
    return (
      <div>
        <h3>{product.name}</h3>
        <p>${product.price}</p>
      </div>
    );
  },
  (prev, next) => {
    // 仅当这些特定字段更改时重新渲染
    return (
      prev.product.id === next.product.id &&
      prev.product.name === next.product.name &&
      prev.product.price === next.product.price
    );
  }
);

useMemo 用于昂贵计算

import { useMemo, useState } from 'react';

function DataTable({ items }: { items: Item[] }) {
  const [filter, setFilter] = useState('');
  const [sortBy, setSortBy] = useState<'name' | 'price'>('name');

  // 昂贵的过滤和排序
  const processedItems = useMemo(() => {
    console.log('计算过滤和排序的项目');
    return items
      .filter(item => item.name.toLowerCase().includes(filter.toLowerCase()))
      .sort((a, b) => {
        if (sortBy === 'name') {
          return a.name.localeCompare(b.name);
        }
        return a.price - b.price;
      });
  }, [items, filter, sortBy]);

  // 昂贵的聚合计算
  const statistics = useMemo(() => {
    console.log('计算统计');
    return {
      total: processedItems.reduce((sum, item) => sum + item.price, 0),
      average: processedItems.length
        ? processedItems.reduce((sum, item) => sum + item.price, 0) / processedItems.length
        : 0,
      count: processedItems.length
    };
  }, [processedItems]);

  return (
    <>
      <input value={filter} onChange={(e) => setFilter(e.target.value)} />
      <select value={sortBy} onChange={(e) => setSortBy(e.target.value as any)}>
        <option value="name">名称</option>
        <option value="price">价格</option>
      </select>
      <div>总计: ${statistics.total}</div>
      <div>平均: ${statistics.average.toFixed(2)}</div>
      <div>数量: {statistics.count}</div>
      {processedItems.map(item => (
        <div key={item.id}>{item.name} - ${item.price}</div>
      ))}
    </>
  );
}

useCallback 用于稳定函数引用

import { useCallback, useState, memo } from 'react';

// 子组件,仅在必要时重新渲染
const ListItem = memo(function ListItem({
  item,
  onDelete
}: {
  item: Item;
  onDelete: (id: string) => void;
}) {
  console.log('渲染 ListItem', item.id);
  return (
    <div>
      {item.name}
      <button onClick={() => onDelete(item.id)}>删除</button>
    </div>
  );
});

function OptimizedList({ items }: { items: Item[] }) {
  const [deletedIds, setDeletedIds] = useState<Set<string>>(new Set());

  // 不使用useCallback,每次渲染都创建新函数,导致即使使用memo,ListItem也会重新渲染
  const handleDelete = useCallback((id: string) => {
    setDeletedIds(prev => new Set([...prev, id]));
    // API调用删除
    api.deleteItem(id);
  }, []); // 空依赖意味着函数从不更改

  const handleDeleteWithDeps = useCallback((id: string) => {
    console.log('已删除:', deletedIds.size);
    setDeletedIds(prev => new Set([...prev, id]));
  }, [deletedIds]); // 当deletedIds更改时重新创建

  const visibleItems = items.filter(item => !deletedIds.has(item.id));

  return (
    <>
      {visibleItems.map(item => (
        <ListItem key={item.id} item={item} onDelete={handleDelete} />
      ))}
    </>
  );
}

使用React.lazy和Suspense进行代码分割

import { lazy, Suspense } from 'react';
import { Routes, Route } from 'react-router-dom';

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

// 回退组件
function LoadingSpinner() {
  return <div className="spinner">加载中...</div>;
}

function App() {
  return (
    <Suspense fallback={<LoadingSpinner />}>
      <Routes>
        <Route path="/dashboard" element={<Dashboard />} />
        <Route path="/profile" element={<Profile />} />
        <Route path="/settings" element={<Settings />} />
        <Route path="/analytics" element={<Analytics />} />
      </Routes>
    </Suspense>
  );
}

// 悬停预加载以改善用户体验
function Navigation() {
  const preloadDashboard = () => import('./pages/Dashboard');
  const preloadProfile = () => import('./pages/Profile');

  return (
    <nav>
      <a href="/dashboard" onMouseEnter={preloadDashboard}>仪表板</a>
      <a href="/profile" onMouseEnter={preloadProfile}>个人资料</a>
    </nav>
  );
}

// 嵌套Suspense边界
function DashboardLayout() {
  const Header = lazy(() => import('./components/Header'));
  const Sidebar = lazy(() => import('./components/Sidebar'));
  const Content = lazy(() => import('./components/Content'));

  return (
    <div className="dashboard">
      <Suspense fallback={<div>加载头部...</div>}>
        <Header />
      </Suspense>
      <Suspense fallback={<div>加载侧边栏...</div>}>
        <Sidebar />
      </Suspense>
      <Suspense fallback={<div>加载内容...</div>}>
        <Content />
      </Suspense>
    </div>
  );
}

大型列表的虚拟滚动

import { FixedSizeList, VariableSizeList } from 'react-window';
import AutoSizer from 'react-virtualized-auto-sizer';

// 固定大小项目
function VirtualList({ items }: { items: string[] }) {
  const Row = ({ index, style }: {
    index: number;
    style: React.CSSProperties
  }) => (
    <div style={style} className="list-item">
      {items[index]}
    </div>
  );

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

// 可变大小项目
function VariableList({ items }: { items: Post[] }) {
  const getItemSize = (index: number) => {
    // 基于内容计算高度
    return items[index].content.length > 100 ? 120 : 80;
  };

  const Row = ({ index, style }: any) => (
    <div style={style} className="post">
      <h3>{items[index].title}</h3>
      <p>{items[index].content}</p>
    </div>
  );

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

// 使用AutoSizer响应式布局
function ResponsiveList({ items }: { items: Item[] }) {
  return (
    <div style={{ height: '100vh', width: '100%' }}>
      <AutoSizer>
        {({ height, width }) => (
          <FixedSizeList
            height={height}
            itemCount={items.length}
            itemSize={50}
            width={width}
          >
            {({ index, style }) => (
              <div style={style}>{items[index].name}</div>
            )}
          </FixedSizeList>
        )}
      </AutoSizer>
    </div>
  );
}

使用React Profiler API进行性能监控

import { Profiler, ProfilerOnRenderCallback } from 'react';

const onRenderCallback: ProfilerOnRenderCallback = (
  id, // 刚刚提交的Profiler树的“id”属性
  phase, // “mount”(首次渲染)或“update”(重新渲染)
  actualDuration, // 渲染提交更新所花费的时间
  baseDuration, // 估计无记忆化渲染整个子树的时间
  startTime, // React开始渲染此更新的时间
  commitTime, // React提交此更新的时间
  interactions // 属于此更新的交互集合
) => {
  console.log(`${id} (${phase}) 花费 ${actualDuration}ms`);

  // 发送到分析
  if (actualDuration > 100) {
    analytics.track('slow-render', {
      component: id,
      duration: actualDuration,
      phase
    });
  }
};

function App() {
  return (
    <Profiler id="App" onRender={onRenderCallback}>
      <Dashboard />
    </Profiler>
  );
}

// 嵌套分析器用于细粒度监控
function Dashboard() {
  return (
    <div>
      <Profiler id="Sidebar" onRender={onRenderCallback}>
        <Sidebar />
      </Profiler>
      <Profiler id="Content" onRender={onRenderCallback}>
        <Content />
      </Profiler>
    </div>
  );
}

包大小优化

// 使用动态导入处理大型库
function ChartComponent() {
  const [Chart, setChart] = useState<any>(null);

  useEffect(() => {
    // 仅当需要时加载图表库
    import('chart.js').then(module => {
      setChart(() => module.Chart);
    });
  }, []);

  if (!Chart) return <div>加载图表...</div>;

  return <Chart data={data} />;
}

// 树可摇动导入
// 良好:仅导入所需内容
import { format } from 'date-fns';

// 不良:导入整个库
import moment from 'moment';

// 使用webpack魔术注释指定块名称
const AdminPanel = lazy(() =>
  import(/* webpackChunkName: "admin" */ './AdminPanel')
);

const UserDashboard = lazy(() =>
  import(/* webpackChunkName: "dashboard" */ './UserDashboard')
);

图像优化技术

import { useState, useEffect } from 'react';

// 懒加载图像
function LazyImage({ src, alt, placeholder }: {
  src: string;
  alt: string;
  placeholder?: string;
}) {
  const [imageSrc, setImageSrc] = useState(placeholder || '');
  const [imageRef, setImageRef] = useState<HTMLImageElement | null>(null);

  useEffect(() => {
    if (!imageRef) return;

    const observer = new IntersectionObserver(entries => {
      entries.forEach(entry => {
        if (entry.isIntersecting) {
          setImageSrc(src);
          observer.unobserve(imageRef);
        }
      });
    });

    observer.observe(imageRef);

    return () => {
      if (imageRef) observer.unobserve(imageRef);
    };
  }, [imageRef, src]);

  return (
    <img
      ref={setImageRef}
      src={imageSrc}
      alt={alt}
      loading="lazy"
    />
  );
}

// 渐进式图像加载
function ProgressiveImage({ src, placeholder }: {
  src: string;
  placeholder: string;
}) {
  const [currentSrc, setCurrentSrc] = useState(placeholder);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    const img = new Image();
    img.src = src;
    img.onload = () => {
      setCurrentSrc(src);
      setLoading(false);
    };
  }, [src]);

  return (
    <img
      src={currentSrc}
      style={{
        filter: loading ? 'blur(10px)' : 'none',
        transition: 'filter 0.3s'
      }}
    />
  );
}

并发特性:useTransition 和 useDeferredValue

import { useState, useTransition, useDeferredValue } from 'react';

// useTransition用于非紧急更新
function SearchResults() {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState<Result[]>([]);
  const [isPending, startTransition] = useTransition();

  const handleSearch = (value: string) => {
    setQuery(value); // 紧急:立即更新输入

    // 非紧急:延迟昂贵搜索
    startTransition(() => {
      const searchResults = performExpensiveSearch(value);
      setResults(searchResults);
    });
  };

  return (
    <>
      <input
        value={query}
        onChange={e => handleSearch(e.target.value)}
        placeholder="搜索..."
      />
      {isPending && <div>搜索中...</div>}
      <ResultsList results={results} />
    </>
  );
}

// useDeferredValue用于延迟昂贵渲染
function ProductList({ products }: { products: Product[] }) {
  const [query, setQuery] = useState('');
  const deferredQuery = useDeferredValue(query);

  // 使用延迟值过滤,保持UI响应
  const filteredProducts = useMemo(() => {
    return products.filter(p =>
      p.name.toLowerCase().includes(deferredQuery.toLowerCase())
    );
  }, [products, deferredQuery]);

  return (
    <>
      <input
        value={query}
        onChange={e => setQuery(e.target.value)}
        placeholder="过滤产品..."
      />
      {query !== deferredQuery && <div>更新中...</div>}
      <div>
        {filteredProducts.map(product => (
          <ProductCard key={product.id} product={product} />
        ))}
      </div>
    </>
  );
}

防抖和节流

import { useState, useEffect, useCallback, useRef } from 'react';

// 自定义防抖钩子
function useDebounce<T>(value: T, delay: number): T {
  const [debouncedValue, setDebouncedValue] = useState<T>(value);

  useEffect(() => {
    const handler = setTimeout(() => {
      setDebouncedValue(value);
    }, delay);

    return () => clearTimeout(handler);
  }, [value, delay]);

  return debouncedValue;
}

// 搜索中使用
function SearchWithDebounce() {
  const [query, setQuery] = useState('');
  const debouncedQuery = useDebounce(query, 500);

  useEffect(() => {
    if (debouncedQuery) {
      // 仅在用户停止输入500毫秒后搜索
      performSearch(debouncedQuery);
    }
  }, [debouncedQuery]);

  return (
    <input
      value={query}
      onChange={e => setQuery(e.target.value)}
      placeholder="搜索..."
    />
  );
}

// 自定义节流钩子
function useThrottle<T>(value: T, limit: number): T {
  const [throttledValue, setThrottledValue] = useState<T>(value);
  const lastRan = useRef(Date.now());

  useEffect(() => {
    const handler = setTimeout(() => {
      if (Date.now() - lastRan.current >= limit) {
        setThrottledValue(value);
        lastRan.current = Date.now();
      }
    }, limit - (Date.now() - lastRan.current));

    return () => clearTimeout(handler);
  }, [value, limit]);

  return throttledValue;
}

// 节流滚动事件
function InfiniteScroll() {
  const [scrollPosition, setScrollPosition] = useState(0);
  const throttledScroll = useThrottle(scrollPosition, 200);

  useEffect(() => {
    const handleScroll = () => setScrollPosition(window.scrollY);
    window.addEventListener('scroll', handleScroll);
    return () => window.removeEventListener('scroll', handleScroll);
  }, []);

  useEffect(() => {
    // 每200毫秒处理一次滚动
    console.log('节流滚动位置:', throttledScroll);
  }, [throttledScroll]);

  return <div>滚动位置: {throttledScroll}</div>;
}

优化上下文性能

import { createContext, useContext, useState, useMemo, ReactNode } from 'react';

// 拆分上下文以防止不必要的重新渲染
const StateContext = createContext<State | null>(null);
const DispatchContext = createContext<Dispatch | null>(null);

function Provider({ children }: { children: ReactNode }) {
  const [state, setState] = useState<State>(initialState);

  // 记忆化dispatch以保持稳定
  const dispatch = useMemo(
    () => ({
      updateUser: (user: User) => setState(s => ({ ...s, user })),
      updateSettings: (settings: Settings) => setState(s => ({ ...s, settings }))
    }),
    []
  );

  return (
    <StateContext.Provider value={state}>
      <DispatchContext.Provider value={dispatch}>
        {children}
      </DispatchContext.Provider>
    </StateContext.Provider>
  );
}

// 组件仅在使用实际更改的状态时重新渲染
function UserProfile() {
  const state = useContext(StateContext); // 任何状态更改时重新渲染
  return <div>{state?.user.name}</div>;
}

function SettingsButton() {
  const dispatch = useContext(DispatchContext); // 从不重新渲染
  return <button onClick={() => dispatch?.updateSettings({})}>设置</button>;
}

使用Web Workers进行繁重计算

import { useEffect, useState } from 'react';

// worker.ts
// self.addEventListener('message', (e) => {
//   const result = performHeavyCalculation(e.data);
//   self.postMessage(result);
// });

function useWebWorker<T, R>(workerFn: (data: T) => R) {
  const [result, setResult] = useState<R | null>(null);
  const [error, setError] = useState<Error | null>(null);
  const [loading, setLoading] = useState(false);

  const execute = (data: T) => {
    setLoading(true);
    setError(null);

    const worker = new Worker(
      URL.createObjectURL(
        new Blob([`(${workerFn.toString()})()`], { type: 'application/javascript' })
      )
    );

    worker.postMessage(data);

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

    worker.onerror = (e) => {
      setError(new Error(e.message));
      setLoading(false);
      worker.terminate();
    };
  };

  return { result, error, loading, execute };
}

// 使用
function DataProcessor() {
  const { result, loading, execute } = useWebWorker(
    (data: number[]) => {
      // 繁重计算在Worker中运行
      return data.reduce((sum, n) => sum + n * n, 0);
    }
  );

  const handleProcess = () => {
    execute(Array.from({ length: 1000000 }, (_, i) => i));
  };

  return (
    <div>
      <button onClick={handleProcess} disabled={loading}>
        处理数据
      </button>
      {loading && <div>处理中...</div>}
      {result && <div>结果: {result}</div>}
    </div>
  );
}

何时使用此技能

使用react-performance时,当您需要:

  • 优化慢渲染组件
  • 通过代码分割减少包大小
  • 使用虚拟化处理大型列表
  • 防止不必要的重新渲染
  • 改善应用程序加载时间
  • 优化昂贵计算
  • 构建高性能React应用程序
  • 调试性能问题
  • 实施懒加载策略
  • 提高核心Web指标分数
  • 优化移动设备
  • 高效处理实时数据

最佳实践

  1. 优化前进行性能分析 - 使用React DevTools Profiler识别实际瓶颈,然后再应用优化。

  2. 明智使用React.memo - 仅记忆化频繁渲染且道具相同的组件,或具有昂贵渲染逻辑的组件。

  3. 记忆化回调和值 - 使用useCallback处理传递给记忆化子组件的函数,useMemo处理昂贵计算。

  4. 按路由进行代码分割 - 懒加载路由组件以减少初始包大小并改善加载时间。

  5. 虚拟化长列表 - 对于超过100个项目的列表,使用react-window或react-virtualized。

  6. 优化图像 - 懒加载图像,使用适当格式(如WebP),实施渐进式加载。

  7. 防抖昂贵操作 - 防抖搜索输入、API调用和其他昂贵操作。

  8. 策略性拆分上下文 - 分离读取和写入上下文以防止不必要的消费者重新渲染。

  9. 监控包大小 - 使用webpack-bundle-analyzer识别和移除大型依赖。

  10. 使用并发特性 - 利用useTransition和useDeferredValue改善感知性能。

常见陷阱

  1. 过早优化 - 不要未经测量就优化。先分析,再优化瓶颈。

  2. 过度使用memo - 记忆化所有内容会增加开销。仅当有可测量益处时记忆化。

  3. 错误的依赖项 - useMemo/useCallback中缺失依赖项会导致闭包陈旧和错误。

  4. 未测量影响 - 始终使用React Profiler或浏览器工具测量性能改进。

  5. 忽略包大小 - 为小功能导入大型库会显著影响加载时间。

  6. 记忆化原始值 - 对于原始值或简单计算,useMemo是不必要的。

  7. 不使用key属性 - 列表中缺少或不正确的key会导致不必要的重新渲染和错误。

  8. 内联函数定义 - 在JSX中内联创建函数会阻止React.memo有效工作。

  9. 未进行代码分割 - 一次性加载整个应用会显著增加初始加载时间。

  10. 忘记网络优化 - 优化数据获取,使用分页,实施适当的缓存策略。

资源