返回教程列表
教程 React 快速入门 第 7 节
进阶
22 分钟

组件优化技巧

学习 React 组件性能优化,掌握 memo、useMemo、useCallback 等技巧

CodeGogo

React.memo

用于防止不必要的重新渲染。

基本用法

const ExpensiveComponent = React.memo(function ExpensiveComponent({ name }) {
  console.log('渲染 ExpensiveComponent');
  return <div>Hello {name}</div>;
});

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

  return (
    <div>
      <button onClick={() => setCount(count + 1)}>点击 {count}</button>
      <ExpensiveComponent name="张三" />
    </div>
  );
}

自定义比较函数

function areEqual(prevProps, nextProps) {
  // 只在 name 改变时重新渲染
  return prevProps.name === nextProps.name;
}

const MemoizedComponent = React.memo(Component, areEqual);

useMemo

缓存计算结果,避免重复计算。

使用场景

function ProductList({ products, filter }) {
  const filteredProducts = useMemo(() => {
    console.log('过滤产品列表...');
    return products.filter(p => p.category === filter);
  }, [products, filter]);

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

不需要使用的情况

// ❌ 不需要 useMemo(计算很简单)
function Component({ a, b }) {
  const result = useMemo(() => a + b, [a, b]);
  return <div>{result}</div>;
}

// ✅ 直接计算即可
function Component({ a, b }) {
  const result = a + b;
  return <div>{result}</div>;
}

useCallback

缓存函数,避免子组件不必要的重新渲染。

基本用法

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

  // 使用 useCallback
  const handleClick = useCallback(() => {
    setCount(c => c + 1);
  }, []); // 空依赖数组,函数只创建一次

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

// 子组件使用 React.memo
const ChildComponent = React.memo(function ChildComponent({ onClick }) {
  console.log('渲染 ChildComponent');
  return <button onClick={onClick}>点击</button>;
});

依赖其他 state

function TodoList({ todos }) {
  const [filter, setFilter] = useState('all');

  const filteredTodos = useMemo(() => {
    return todos.filter(todo => {
      if (filter === 'active') return !todo.completed;
      if (filter === 'completed') return todo.completed;
      return true;
    });
  }, [todos, filter]);

  const handleToggle = useCallback((id) => {
    setTodos(prev => prev.map(todo =>
      todo.id === id ? { ...todo, completed: !todo.completed } : todo
    ));
  }, [setTodos]);

  return (
    <div>
      <button onClick={() => setFilter('all')}>全部</button>
      <button onClick={() => setFilter('active')}>未完成</button>
      <button onClick={() => setFilter('completed')}>已完成</button>
      <TodoList items={filteredTodos} onToggle={handleToggle} />
    </div>
  );
}

代码分割和懒加载

React.lazy

import { lazy, Suspense } from 'react';

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

function App() {
  return (
    <Suspense fallback={<div>加载中...</div>}>
      <HeavyComponent />
    </Suspense>
  );
}

基于路由的代码分割

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

const Home = lazy(() => import('./routes/Home'));
const About = lazy(() => import('./routes/About'));

function App() {
  return (
    <BrowserRouter>
      <nav>
        <Link to="/">首页</Link>
        <Link to="/about">关于</Link>
      </nav>
      <Suspense fallback={<div>加载中...</div>}>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/about" element={<About />} />
        </Routes>
      </Suspense>
    </BrowserRouter>
  );
}

虚拟化长列表

使用 react-window 渲染大量数据。

import { FixedSizeList } from 'react-window';

function Row({ index, style }) {
  return (
    <div style={style}>
      第 {index} 行
    </div>
  );
}

function VirtualizedList({ items }) {
  return (
    <FixedSizeList
      height={400}
      width={300}
      itemCount={items.length}
      itemSize={35}
    >
      {Row}
    </FixedSizeList>
  );
}

💡 最佳实践

  1. 测量优化前先测量:使用 React DevTools Profiler
  2. 不要过度优化:只有遇到性能问题才优化
  3. 保持组件简单:小组件更容易优化
  4. 避免不必要的 state:减少渲染次数

🎯 练习

  1. 使用 React.memo 优化一个列表组件
  2. 使用 useMemo 缓存复杂的计算
  3. 实现路由级别的代码分割

下一节我们将学习 React 项目实战

觉得这个教程有帮助?

继续学习更多教程,掌握编程技能

查看更多教程