01
Apr

React 学习笔记(五):性能优化实战

avatar
ByKK
2025-04-01/6 Min Read
0
0
(AI总结) React 应用的性能优化是一个系统工程,需要从多个层面进行优化。本篇将介绍 React 性能优化的核心概念、工具和实践技巧,帮助你构建高性能的 React 应用。

React 应用的性能优化需要从多个角度考虑:组件渲染、数据获取、代码分割等。本篇将介绍实用的性能优化技巧。

性能优化的基本原则

1. 测量优先

在优化之前,先使用工具测量性能瓶颈:

  • React DevTools Profiler
  • Chrome DevTools Performance
  • Lighthouse

2. 避免过早优化

先确保功能正确,再考虑性能优化。

3. 关注用户体验

优化应该以提升用户体验为目标,而不是单纯的数字指标。

React DevTools Profiler 使用

Profiler 是 React 内置的性能分析工具。

import { Profiler } from 'react';
 
function onRenderCallback(
  id: string,
  phase: 'mount' | 'update',
  actualDuration: number,
  baseDuration: number,
  startTime: number,
  commitTime: number
) {
  console.log(`组件 ${id}${phase} 阶段耗时 ${actualDuration}ms`);
}
 
function App() {
  return (
    <Profiler id="App" onRender={onRenderCallback}>
      <Header />
      <Main />
      <Footer />
    </Profiler>
  );
}

组件渲染优化

1. React.memo

防止不必要的重新渲染:

import { memo } from 'react';
 
interface UserCardProps {
  user: User;
  onEdit: (id: number) => void;
}
 
const UserCard = memo<UserCardProps>(({ user, onEdit }) => {
  console.log(`UserCard ${user.id} 重新渲染`);
  
  return (
    <div className="user-card">
      <h3>{user.name}</h3>
      <p>{user.email}</p>
      <button onClick={() => onEdit(user.id)}>编辑</button>
    </div>
  );
});
 
// 使用示例
function UserList() {
  const [users, setUsers] = useState<User[]>([]);
  
  const handleEdit = useCallback((id: number) => {
    console.log('编辑用户:', id);
  }, []);
 
  return (
    <div>
      {users.map(user => (
        <UserCard 
          key={user.id} 
          user={user} 
          onEdit={handleEdit}
        />
      ))}
    </div>
  );
}

2. useMemo 优化计算

缓存昂贵的计算结果:

import { useMemo } from 'react';
 
function ExpensiveComponent({ data, filter }) {
  // 缓存过滤和排序的结果
  const processedData = useMemo(() => {
    console.log('重新计算数据...');
    return data
      .filter(item => item.name.includes(filter))
      .sort((a, b) => a.name.localeCompare(b.name));
  }, [data, filter]);
 
  return (
    <div>
      {processedData.map(item => (
        <div key={item.id}>{item.name}</div>
      ))}
    </div>
  );
}

3. useCallback 优化函数

缓存函数引用:

import { useCallback } from 'react';
 
function ParentComponent() {
  const [count, setCount] = useState(0);
  
  // 缓存函数引用
  const handleClick = useCallback(() => {
    console.log('按钮被点击');
  }, []);
 
  const handleSubmit = useCallback((data) => {
    console.log('提交数据:', data);
  }, []);
 
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(c => c + 1)}>
        增加计数
      </button>
      <ChildComponent 
        onButtonClick={handleClick}
        onSubmit={handleSubmit}
      />
    </div>
  );
}
 
const ChildComponent = memo(({ onButtonClick, onSubmit }) => {
  console.log('ChildComponent 重新渲染');
  
  return (
    <div>
      <button onClick={onButtonClick}>子组件按钮</button>
    </div>
  );
});

列表渲染优化

1. 虚拟化长列表

使用 react-windowreact-virtualized

import { FixedSizeList as List } from 'react-window';
 
function VirtualizedList({ items }) {
  const Row = ({ index, style }) => (
    <div style={style}>
      <div className="list-item">
        <h3>{items[index].name}</h3>
        <p>{items[index].description}</p>
      </div>
    </div>
  );
 
  return (
    <List
      height={400}
      itemCount={items.length}
      itemSize={80}
      width="100%"
    >
      {Row}
    </List>
  );
}
 
// 使用示例
function App() {
  const [items, setItems] = useState([]);
  
  useEffect(() => {
    // 模拟大量数据
    const mockItems = Array.from({ length: 10000 }, (_, i) => ({
      id: i,
      name: `项目 ${i}`,
      description: `这是项目 ${i} 的描述`
    }));
    setItems(mockItems);
  }, []);
 
  return <VirtualizedList items={items} />;
}

2. 分页加载

function PaginatedList() {
  const [items, setItems] = useState([]);
  const [page, setPage] = useState(1);
  const [loading, setLoading] = useState(false);
  const [hasMore, setHasMore] = useState(true);
 
  const loadMore = useCallback(async () => {
    if (loading || !hasMore) return;
    
    setLoading(true);
    try {
      const response = await fetch(`/api/items?page=${page}&limit=20`);
      const newItems = await response.json();
      
      setItems(prev => [...prev, ...newItems]);
      setPage(prev => prev + 1);
      setHasMore(newItems.length === 20);
    } catch (error) {
      console.error('加载失败:', error);
    } finally {
      setLoading(false);
    }
  }, [page, loading, hasMore]);
 
  return (
    <div>
      <div className="items-list">
        {items.map(item => (
          <div key={item.id} className="item">
            {item.name}
          </div>
        ))}
      </div>
      
      {hasMore && (
        <button 
          onClick={loadMore} 
          disabled={loading}
          className="load-more-btn"
        >
          {loading ? '加载中...' : '加载更多'}
        </button>
      )}
    </div>
  );
}

数据获取优化

1. 使用 TanStack Query

import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
 
function UserProfile({ userId }) {
  const queryClient = useQueryClient();
  
  // 获取用户数据
  const { data: user, isLoading, error } = useQuery({
    queryKey: ['user', userId],
    queryFn: () => fetch(`/api/users/${userId}`).then(res => res.json()),
    staleTime: 5 * 60 * 1000, // 5分钟内数据不重新获取
    cacheTime: 10 * 60 * 1000, // 缓存10分钟
  });
 
  // 更新用户数据
  const updateUserMutation = useMutation({
    mutationFn: (userData) => 
      fetch(`/api/users/${userId}`, {
        method: 'PUT',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(userData),
      }).then(res => res.json()),
    onSuccess: (updatedUser) => {
      // 更新缓存
      queryClient.setQueryData(['user', userId], updatedUser);
    },
  });
 
  if (isLoading) return <div>加载中...</div>;
  if (error) return <div>错误: {error.message}</div>;
 
  return (
    <div>
      <h1>{user.name}</h1>
      <p>{user.email}</p>
      <button 
        onClick={() => updateUserMutation.mutate({ name: '新名字' })}
        disabled={updateUserMutation.isPending}
      >
        {updateUserMutation.isPending ? '更新中...' : '更新'}
      </button>
    </div>
  );
}

2. 预加载数据

function App() {
  const queryClient = useQueryClient();
  
  // 预加载用户数据
  const prefetchUser = (userId) => {
    queryClient.prefetchQuery({
      queryKey: ['user', userId],
      queryFn: () => fetch(`/api/users/${userId}`).then(res => res.json()),
    });
  };
 
  return (
    <div>
      <nav>
        <Link 
          to="/users/1" 
          onMouseEnter={() => prefetchUser(1)}
        >
          用户1
        </Link>
        <Link 
          to="/users/2" 
          onMouseEnter={() => prefetchUser(2)}
        >
          用户2
        </Link>
      </nav>
      <Outlet />
    </div>
  );
}

代码分割和懒加载

1. React.lazy

import { lazy, Suspense } from 'react';
 
// 懒加载组件
const UserDashboard = lazy(() => import('./UserDashboard'));
const AdminPanel = lazy(() => import('./AdminPanel'));
 
function App() {
  const [userRole, setUserRole] = useState('user');
 
  return (
    <div>
      <Suspense fallback={<div>加载中...</div>}>
        {userRole === 'admin' ? <AdminPanel /> : <UserDashboard />}
      </Suspense>
    </div>
  );
}

2. 路由级别的代码分割

import { lazy } from 'react';
import { Routes, Route } from 'react-router-dom';
 
const Home = lazy(() => import('./pages/Home'));
const About = lazy(() => import('./pages/About'));
const Contact = lazy(() => import('./pages/Contact'));
 
function App() {
  return (
    <Suspense fallback={<div>页面加载中...</div>}>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
        <Route path="/contact" element={<Contact />} />
      </Routes>
    </Suspense>
  );
}

Bundle 分析和优化

1. 使用 webpack-bundle-analyzer

# 安装
npm install --save-dev webpack-bundle-analyzer
 
# 分析构建结果
npm run build
npx webpack-bundle-analyzer build/static/js/*.js

2. 优化依赖

// 避免导入整个库
// 错误
import _ from 'lodash';
 
// 正确
import debounce from 'lodash/debounce';
 
// 使用动态导入
const loadHeavyLibrary = async () => {
  const { default: heavyLibrary } = await import('./heavyLibrary');
  return heavyLibrary;
};

内存泄漏预防

1. 清理事件监听器

function EventListenerComponent() {
  useEffect(() => {
    const handleResize = () => {
      console.log('窗口大小改变');
    };
 
    window.addEventListener('resize', handleResize);
    
    // 清理函数
    return () => {
      window.removeEventListener('resize', handleResize);
    };
  }, []);
}

2. 清理定时器

function TimerComponent() {
  useEffect(() => {
    const timer = setInterval(() => {
      console.log('定时器执行');
    }, 1000);
 
    return () => {
      clearInterval(timer);
    };
  }, []);
}

3. 清理异步操作

function AsyncComponent() {
  useEffect(() => {
    let isMounted = true;
 
    const fetchData = async () => {
      try {
        const data = await fetch('/api/data');
        if (isMounted) {
          // 只有在组件仍然挂载时才更新状态
          setData(await data.json());
        }
      } catch (error) {
        if (isMounted) {
          setError(error);
        }
      }
    };
 
    fetchData();
 
    return () => {
      isMounted = false;
    };
  }, []);
}

性能监控

1. 使用 React Profiler API

import { Profiler } from 'react';
 
function App() {
  const handleProfilerRender = (
    id,
    phase,
    actualDuration,
    baseDuration,
    startTime,
    commitTime
  ) => {
    // 发送性能数据到分析服务
    if (actualDuration > 16) { // 超过一帧的时间
      console.warn(`组件 ${id} 渲染时间过长: ${actualDuration}ms`);
    }
  };
 
  return (
    <Profiler id="App" onRender={handleProfilerRender}>
      <Header />
      <Main />
      <Footer />
    </Profiler>
  );
}

2. 自定义性能 Hook

function usePerformanceMonitor(componentName) {
  useEffect(() => {
    const startTime = performance.now();
    
    return () => {
      const endTime = performance.now();
      const duration = endTime - startTime;
      
      if (duration > 100) {
        console.warn(`${componentName} 挂载时间过长: ${duration}ms`);
      }
    };
  });
}
 
function ExpensiveComponent() {
  usePerformanceMonitor('ExpensiveComponent');
  
  return <div>复杂组件</div>;
}

总结

  • 测量优先:使用 Profiler 和性能工具识别瓶颈
  • 组件优化:合理使用 memo、useMemo、useCallback
  • 列表优化:虚拟化长列表,实现分页加载
  • 数据优化:使用 TanStack Query 管理服务端状态
  • 代码分割:按需加载,减少初始包大小
  • 内存管理:及时清理事件监听器和定时器
  • 持续监控:建立性能监控体系

掌握这些优化技巧,你就能构建出高性能的 React 应用。