react-performance

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

React Performance Optimization Skill

React性能优化技能

Overview

概述

Master React performance optimization techniques including memoization, code splitting, lazy loading, and profiling tools.
掌握React性能优化技术,包括记忆化、代码分割、懒加载和性能分析工具。

Learning Objectives

学习目标

  • Identify performance bottlenecks
  • Use React DevTools Profiler
  • Implement memoization strategies
  • Apply code splitting techniques
  • Optimize bundle size
  • 识别性能瓶颈
  • 使用React DevTools Profiler
  • 实现记忆化策略
  • 应用代码分割技术
  • 优化包体积

React DevTools Profiler

React DevTools Profiler

Profiling Steps

性能分析步骤

  1. Open React DevTools
  2. Go to Profiler tab
  3. Click record button
  4. Interact with app
  5. Stop recording
  6. Analyze flame graph
  1. 打开React DevTools
  2. 切换到Profiler标签页
  3. 点击录制按钮
  4. 与应用交互
  5. 停止录制
  6. 分析火焰图

Reading Results

结果解读

  • Render duration: Time spent rendering
  • Commits: Number of renders
  • Flame graph: Visual render tree
  • Ranked chart: Components by render time
  • 渲染时长:渲染所花费的时间
  • 提交次数:渲染的次数
  • 火焰图:可视化的渲染树
  • 排名图表:按渲染时间排序的组件

Memoization

记忆化

React.memo

React.memo

jsx
// Prevent re-render if props unchanged
const ExpensiveComponent = React.memo(function ExpensiveComponent({ data }) {
  return <div>{data.value}</div>;
});

// Custom comparison
const Component = React.memo(
  ({ user }) => <div>{user.name}</div>,
  (prevProps, nextProps) => {
    return prevProps.user.id === nextProps.user.id;
  }
);
jsx
// Prevent re-render if props unchanged
const ExpensiveComponent = React.memo(function ExpensiveComponent({ data }) {
  return <div>{data.value}</div>;
});

// Custom comparison
const Component = React.memo(
  ({ user }) => <div>{user.name}</div>,
  (prevProps, nextProps) => {
    return prevProps.user.id === nextProps.user.id;
  }
);

useMemo

useMemo

jsx
function ProductList({ products, searchTerm }) {
  // Only re-filter when dependencies change
  const filteredProducts = useMemo(() => {
    console.log('Filtering...');
    return products.filter(p =>
      p.name.toLowerCase().includes(searchTerm.toLowerCase())
    );
  }, [products, searchTerm]);

  return <div>{filteredProducts.map(renderProduct)}</div>;
}
jsx
function ProductList({ products, searchTerm }) {
  // Only re-filter when dependencies change
  const filteredProducts = useMemo(() => {
    console.log('Filtering...');
    return products.filter(p =>
      p.name.toLowerCase().includes(searchTerm.toLowerCase())
    );
  }, [products, searchTerm]);

  return <div>{filteredProducts.map(renderProduct)}</div>;
}

useCallback

useCallback

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

  // Stable function reference
  const handleClick = useCallback(() => {
    setCount(c => c + 1);
  }, []);

  return <MemoizedChild onClick={handleClick} />;
}

const MemoizedChild = React.memo(({ onClick }) => {
  console.log('Child rendered');
  return <button onClick={onClick}>Click</button>;
});
jsx
function Parent() {
  const [count, setCount] = useState(0);

  // Stable function reference
  const handleClick = useCallback(() => {
    setCount(c => c + 1);
  }, []);

  return <MemoizedChild onClick={handleClick} />;
}

const MemoizedChild = React.memo(({ onClick }) => {
  console.log('Child rendered');
  return <button onClick={onClick}>Click</button>;
});

Code Splitting

代码分割

Route-Based Splitting

基于路由的分割

jsx
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={<LoadingSpinner />}>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/dashboard" element={<Dashboard />} />
        <Route path="/settings" element={<Settings />} />
      </Routes>
    </Suspense>
  );
}
jsx
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={<LoadingSpinner />}>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/dashboard" element={<Dashboard />} />
        <Route path="/settings" element={<Settings />} />
      </Routes>
    </Suspense>
  );
}

Component-Based Splitting

基于组件的分割

jsx
function ProductPage() {
  const [showReviews, setShowReviews] = useState(false);

  const Reviews = lazy(() => import('./Reviews'));

  return (
    <div>
      <ProductInfo />
      <button onClick={() => setShowReviews(true)}>
        Show Reviews
      </button>
      {showReviews && (
        <Suspense fallback={<Spinner />}>
          <Reviews />
        </Suspense>
      )}
    </div>
  );
}
jsx
function ProductPage() {
  const [showReviews, setShowReviews] = useState(false);

  const Reviews = lazy(() => import('./Reviews'));

  return (
    <div>
      <ProductInfo />
      <button onClick={() => setShowReviews(true)}>
        Show Reviews
      </button>
      {showReviews && (
        <Suspense fallback={<Spinner />}>
          <Reviews />
        </Suspense>
      )}
    </div>
  );
}

List Virtualization

列表虚拟化

React Window

React Window

jsx
import { FixedSizeList } from 'react-window';

function VirtualList({ items }) {
  return (
    <FixedSizeList
      height={600}
      itemCount={items.length}
      itemSize={50}
      width="100%"
    >
      {({ index, style }) => (
        <div style={style}>
          {items[index].name}
        </div>
      )}
    </FixedSizeList>
  );
}
jsx
import { FixedSizeList } from 'react-window';

function VirtualList({ items }) {
  return (
    <FixedSizeList
      height={600}
      itemCount={items.length}
      itemSize={50}
      width="100%"
    >
      {({ index, style }) => (
        <div style={style}>
          {items[index].name}
        </div>
      )}
    </FixedSizeList>
  );
}

Image Optimization

图片优化

Lazy Loading

懒加载

jsx
function LazyImage({ src, alt }) {
  const [isLoaded, setIsLoaded] = useState(false);
  const [isInView, setIsInView] = useState(false);
  const imgRef = useRef();

  useEffect(() => {
    const observer = new IntersectionObserver(
      ([entry]) => {
        if (entry.isIntersecting) {
          setIsInView(true);
          observer.disconnect();
        }
      },
      { threshold: 0.1 }
    );

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

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

  return (
    <div ref={imgRef}>
      {isInView && (
        <img
          src={src}
          alt={alt}
          onLoad={() => setIsLoaded(true)}
          style={{ opacity: isLoaded ? 1 : 0 }}
        />
      )}
    </div>
  );
}
jsx
function LazyImage({ src, alt }) {
  const [isLoaded, setIsLoaded] = useState(false);
  const [isInView, setIsInView] = useState(false);
  const imgRef = useRef();

  useEffect(() => {
    const observer = new IntersectionObserver(
      ([entry]) => {
        if (entry.isIntersecting) {
          setIsInView(true);
          observer.disconnect();
        }
      },
      { threshold: 0.1 }
    );

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

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

  return (
    <div ref={imgRef}>
      {isInView && (
        <img
          src={src}
          alt={alt}
          onLoad={() => setIsLoaded(true)}
          style={{ opacity: isLoaded ? 1 : 0 }}
        />
      )}
    </div>
  );
}

Bundle Optimization

包优化

Tree Shaking

Tree Shaking

jsx
// Good: Named imports
import { Button, Input } from 'components';

// Bad: Import everything
import * as Components from 'components';
jsx
// Good: Named imports
import { Button, Input } from 'components';

// Bad: Import everything
import * as Components from 'components';

Dynamic Imports

动态导入

jsx
async function exportData() {
  const XLSX = await import('xlsx');
  // Use XLSX only when needed
}
jsx
async function exportData() {
  const XLSX = await import('xlsx');
  // Use XLSX only when needed
}

Bundle Analysis

包分析

bash
undefined
bash
undefined

Create React App

Create React App

npm install --save-dev source-map-explorer npm run build npx source-map-explorer 'build/static/js/*.js'
npm install --save-dev source-map-explorer npm run build npx source-map-explorer 'build/static/js/*.js'

Vite

Vite

npm install --save-dev rollup-plugin-visualizer
undefined
npm install --save-dev rollup-plugin-visualizer
undefined

Common Mistakes

常见错误

jsx
// ❌ Overusing useMemo
const greeting = useMemo(() => `Hello ${name}`, [name]);

// ✅ Just compute
const greeting = `Hello ${name}`;

// ❌ Inline objects in deps
useEffect(() => {
  fetchData(filters);
}, [filters]); // New object every render!

// ✅ Memoize object
const filters = useMemo(() => ({ category, sort }), [category, sort]);

// ❌ Creating functions in render
{items.map(item => <Item onClick={() => handle(item.id)} />)}

// ✅ Use useCallback
const handle = useCallback((id) => {...}, []);
{items.map(item => <Item onClick={handle} id={item.id} />)}
jsx
// ❌ Overusing useMemo
const greeting = useMemo(() => `Hello ${name}`, [name]);

// ✅ Just compute
const greeting = `Hello ${name}`;

// ❌ Inline objects in deps
useEffect(() => {
  fetchData(filters);
}, [filters]); // New object every render!

// ✅ Memoize object
const filters = useMemo(() => ({ category, sort }), [category, sort]);

// ❌ Creating functions in render
{items.map(item => <Item onClick={() => handle(item.id)} />)}

// ✅ Use useCallback
const handle = useCallback((id) => {...}, []);
{items.map(item => <Item onClick={handle} id={item.id} />)}

Web Vitals

Web Vitals

Measuring Performance

性能测量

jsx
import { getCLS, getFID, getFCP, getLCP, getTTFB } from 'web-vitals';

function sendToAnalytics(metric) {
  console.log(metric);
}

getCLS(sendToAnalytics);
getFID(sendToAnalytics);
getFCP(sendToAnalytics);
getLCP(sendToAnalytics);
getTTFB(sendToAnalytics);
jsx
import { getCLS, getFID, getFCP, getLCP, getTTFB } from 'web-vitals';

function sendToAnalytics(metric) {
  console.log(metric);
}

getCLS(sendToAnalytics);
getFID(sendToAnalytics);
getFCP(sendToAnalytics);
getLCP(sendToAnalytics);
getTTFB(sendToAnalytics);

Best Practices

最佳实践

  1. Profile before optimizing
  2. Use React.memo for expensive components
  3. Memoize only when beneficial
  4. Split code at route boundaries
  5. Virtualize long lists
  6. Lazy load images
  7. Monitor bundle size
  1. 先分析再优化
  2. 为开销大的组件使用React.memo
  3. 仅在有益时使用记忆化
  4. 在路由边界处分割代码
  5. 虚拟化长列表
  6. 懒加载图片
  7. 监控包体积

When to Optimize

何时进行优化

DO Optimize

应该优化的场景

  • Frequent renders
  • Large lists (>100 items)
  • Expensive calculations
  • Heavy component trees
  • 频繁渲染的组件
  • 长列表(>100条数据)
  • 开销大的计算
  • 复杂的组件树

DON'T Optimize

无需优化的场景

  • Simple components
  • Rare renders
  • Cheap calculations
  • Small lists (<50 items)
  • 简单组件
  • 很少渲染的组件
  • 开销小的计算
  • 短列表(<50条数据)

Practice Exercises

实践练习

  1. Profile app with React DevTools
  2. Optimize large product list
  3. Implement code splitting
  4. Build virtualized table
  5. Optimize image gallery
  6. Reduce bundle size
  7. Measure Web Vitals
  1. 使用React DevTools分析应用
  2. 优化大型产品列表
  3. 实现代码分割
  4. 构建虚拟化表格
  5. 优化图片画廊
  6. 减小包体积
  7. 测量Web Vitals

Resources

参考资源


Difficulty: Advanced Estimated Time: 2-3 weeks Prerequisites: React Hooks, Component Architecture

难度:高级 预计学习时间:2-3周 前置知识:React Hooks、组件架构