Vue 开发者的 React 实战指南:性能优化篇

作为 Vue 开发者,在迁移到 React 开发时,性能优化的思路和方法会有所不同。本文将从 Vue 开发者熟悉的角度出发,详细介绍 React 中的性能优化策略。

渲染优化对比

Vue 的响应式系统

Vue 通过响应式系统自动追踪依赖,只有在数据真正变化时才会触发重渲染

vue 复制代码
<template>
  <div>
    <h1>{{ title }}</h1>
    <p>{{ description }}</p>
    <!-- 只有 count 变化时才会重渲染 -->
    <div>点击次数:{{ count }}</div>
    <button @click="increment">+1</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      title: '标题',
      description: '描述',
      count: 0
    }
  },
  methods: {
    increment() {
      this.count++
    }
  }
}
</script>

React 的渲染机制

React 默认采用自上而下的渲染策略,父组件更新会触发子组件重渲染:

jsx 复制代码
function App() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <h1>标题</h1>
      <p>描述</p>
      {/* 每次 count 变化,整个组件树都会重新渲染 */}
      <div>点击次数:{count}</div>
      <button onClick={() => setCount(count + 1)}>+1</button>
    </div>
  );
}

优化后的版本:

jsx 复制代码
const Title = memo(function Title() {
  return <h1>标题</h1>;
});

const Description = memo(function Description() {
  return <p>描述</p>;
});

const Counter = memo(function Counter({ count, onIncrement }) {
  return (
    <>
      <div>点击次数:{count}</div>
      <button onClick={onIncrement}>+1</button>
    </>
  );
});

function App() {
  const [count, setCount] = useState(0);
  const increment = useCallback(() => {
    setCount(c => c + 1);
  }, []);

  return (
    <div>
      <Title />
      <Description />
      <Counter count={count} onIncrement={increment} />
    </div>
  );
}

组件优化策略

1. 组件拆分与记忆化

jsx 复制代码
// 不好的实践
function ProductList({ products, onSelect }) {
  return (
    <div>
      {products.map(product => (
        <div key={product.id} onClick={() => onSelect(product)}>
          <img src={product.image} alt={product.name} />
          <h3>{product.name}</h3>
          <p>{product.price}</p>
        </div>
      ))}
    </div>
  );
}

// 好的实践
const ProductItem = memo(function ProductItem({ product, onSelect }) {
  const handleClick = useCallback(() => {
    onSelect(product);
  }, [product, onSelect]);

  return (
    <div onClick={handleClick}>
      <img src={product.image} alt={product.name} />
      <h3>{product.name}</h3>
      <p>{product.price}</p>
    </div>
  );
});

function ProductList({ products, onSelect }) {
  return (
    <div>
      {products.map(product => (
        <ProductItem
          key={product.id}
          product={product}
          onSelect={onSelect}
        />
      ))}
    </div>
  );
}

2. 状态管理优化

jsx 复制代码
// 不好的实践
function Dashboard() {
  const [state, setState] = useState({
    user: null,
    products: [],
    orders: [],
    settings: {}
  });

  // 任何状态更新都会导致整个组件重渲染
  const updateUser = (user) => {
    setState(prev => ({ ...prev, user }));
  };

  return (
    <div>
      <UserProfile user={state.user} onUpdate={updateUser} />
      <ProductList products={state.products} />
      <OrderList orders={state.orders} />
      <Settings settings={state.settings} />
    </div>
  );
}

// 好的实践
function Dashboard() {
  const [user, setUser] = useState(null);
  const [products, setProducts] = useState([]);
  const [orders, setOrders] = useState([]);
  const [settings, setSettings] = useState({});

  return (
    <div>
      <UserProfile user={user} onUpdate={setUser} />
      <ProductList products={products} />
      <OrderList orders={orders} />
      <Settings settings={settings} />
    </div>
  );
}

3. 计算属性优化

jsx 复制代码
// 不好的实践
function OrderSummary({ orders }) {
  // 每次渲染都会重新计算
  const totalAmount = orders.reduce((sum, order) => sum + order.amount, 0);
  const completedOrders = orders.filter(order => order.status === 'completed');
  const pendingOrders = orders.filter(order => order.status === 'pending');

  return (
    <div>
      <p>总金额:{totalAmount}</p>
      <p>已完成订单:{completedOrders.length}</p>
      <p>待处理订单:{pendingOrders.length}</p>
    </div>
  );
}

// 好的实践
function OrderSummary({ orders }) {
  const totalAmount = useMemo(() => {
    return orders.reduce((sum, order) => sum + order.amount, 0);
  }, [orders]);

  const { completedOrders, pendingOrders } = useMemo(() => {
    return {
      completedOrders: orders.filter(order => order.status === 'completed'),
      pendingOrders: orders.filter(order => order.status === 'pending')
    };
  }, [orders]);

  return (
    <div>
      <p>总金额:{totalAmount}</p>
      <p>已完成订单:{completedOrders.length}</p>
      <p>待处理订单:{pendingOrders.length}</p>
    </div>
  );
}

列表渲染优化

1. 虚拟列表

jsx 复制代码
function VirtualList({
  items,
  itemHeight,
  windowHeight,
  overscan = 3
}) {
  const [scrollTop, setScrollTop] = useState(0);
  const containerRef = useRef();

  const visibleCount = Math.ceil(windowHeight / itemHeight);
  const totalHeight = items.length * itemHeight;

  const startIndex = Math.max(0, Math.floor(scrollTop / itemHeight) - overscan);
  const endIndex = Math.min(
    items.length,
    Math.ceil((scrollTop + windowHeight) / itemHeight) + overscan
  );

  const visibleItems = useMemo(() => {
    return items.slice(startIndex, endIndex).map((item, index) => ({
      ...item,
      index: startIndex + index
    }));
  }, [items, startIndex, endIndex]);

  const handleScroll = useCallback((e) => {
    setScrollTop(e.target.scrollTop);
  }, []);

  return (
    <div
      ref={containerRef}
      style={{ height: windowHeight, overflow: 'auto' }}
      onScroll={handleScroll}
    >
      <div style={{ height: totalHeight, position: 'relative' }}>
        {visibleItems.map(item => (
          <div
            key={item.id}
            style={{
              position: 'absolute',
              top: item.index * itemHeight,
              height: itemHeight
            }}
          >
            {item.content}
          </div>
        ))}
      </div>
    </div>
  );
}

2. 无限滚动

jsx 复制代码
function InfiniteList({ fetchItems, itemHeight = 50 }) {
  const [items, setItems] = useState([]);
  const [loading, setLoading] = useState(false);
  const [hasMore, setHasMore] = useState(true);
  const [page, setPage] = useState(1);
  const containerRef = useRef();

  const loadMore = useCallback(async () => {
    if (loading || !hasMore) return;

    setLoading(true);
    try {
      const newItems = await fetchItems(page);
      if (newItems.length === 0) {
        setHasMore(false);
      } else {
        setItems(prev => [...prev, ...newItems]);
        setPage(p => p + 1);
      }
    } finally {
      setLoading(false);
    }
  }, [fetchItems, page, loading, hasMore]);

  useEffect(() => {
    const container = containerRef.current;
    if (!container) return;

    const observer = new IntersectionObserver(
      entries => {
        if (entries[0].isIntersecting) {
          loadMore();
        }
      },
      { threshold: 0.5 }
    );

    const sentinel = container.lastElementChild;
    if (sentinel) {
      observer.observe(sentinel);
    }

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

  return (
    <div ref={containerRef} style={{ height: '100vh', overflow: 'auto' }}>
      {items.map(item => (
        <div key={item.id} style={{ height: itemHeight }}>
          {item.content}
        </div>
      ))}
      {hasMore && (
        <div style={{ height: itemHeight, textAlign: 'center' }}>
          {loading ? '加载中...' : '向下滚动加载更多'}
        </div>
      )}
    </div>
  );
}

数据获取优化

1. 请求缓存

jsx 复制代码
function useQuery(key, fetcher, options = {}) {
  const cache = useRef(new Map());
  const [data, setData] = useState(null);
  const [error, setError] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    const fetchData = async () => {
      if (cache.current.has(key) && !options.forceRefetch) {
        setData(cache.current.get(key));
        setLoading(false);
        return;
      }

      setLoading(true);
      try {
        const result = await fetcher();
        cache.current.set(key, result);
        setData(result);
        setError(null);
      } catch (err) {
        setError(err);
        setData(null);
      } finally {
        setLoading(false);
      }
    };

    fetchData();
  }, [key, fetcher, options.forceRefetch]);

  return { data, error, loading };
}

2. 请求去重

jsx 复制代码
function useDedupeQuery(key, fetcher) {
  const pendingRequests = useRef(new Map());

  const executeQuery = useCallback(async () => {
    if (pendingRequests.current.has(key)) {
      return pendingRequests.current.get(key);
    }

    const promise = fetcher();
    pendingRequests.current.set(key, promise);

    try {
      const result = await promise;
      pendingRequests.current.delete(key);
      return result;
    } catch (error) {
      pendingRequests.current.delete(key);
      throw error;
    }
  }, [key, fetcher]);

  return useQuery(key, executeQuery);
}

代码分割

1. 路由级别分割

jsx 复制代码
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Profile = lazy(() => import('./pages/Profile'));
const Settings = lazy(() => import('./pages/Settings'));

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

2. 组件级别分割

jsx 复制代码
const HeavyChart = lazy(() => import('./components/HeavyChart'));

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

  return (
    <div>
      <button onClick={() => setShowChart(true)}>显示图表</button>
      {showChart && (
        <Suspense fallback={<Loading />}>
          <HeavyChart />
        </Suspense>
      )}
    </div>
  );
}

工具和监控

1. 性能分析

jsx 复制代码
import { Profiler } from 'react';

function onRenderCallback(
  id,
  phase,
  actualDuration,
  baseDuration,
  startTime,
  commitTime,
  interactions
) {
  console.log({
    id,
    phase,
    actualDuration,
    baseDuration,
    startTime,
    commitTime,
    interactions
  });
}

function App() {
  return (
    <Profiler id="App" onRender={onRenderCallback}>
      <div>
        {/* 应用内容 */}
      </div>
    </Profiler>
  );
}

2. 性能监控

jsx 复制代码
function usePerformanceMonitor() {
  useEffect(() => {
    const observer = new PerformanceObserver((list) => {
      for (const entry of list.getEntries()) {
        if (entry.entryType === 'largest-contentful-paint') {
          console.log('LCP:', entry.startTime);
        }
        if (entry.entryType === 'first-input') {
          console.log('FID:', entry.processingStart - entry.startTime);
        }
        if (entry.entryType === 'layout-shift') {
          console.log('CLS:', entry.value);
        }
      }
    });

    observer.observe({
      entryTypes: ['largest-contentful-paint', 'first-input', 'layout-shift']
    });

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

最佳实践

  1. 渲染优化

    • 合理拆分组件
    • 使用 memo 避免不必要的重渲染
    • 优化计算属性
    • 合理使用 Context
  2. 状态管理

    • 状态粒度适中
    • 避免冗余状态
    • 使用不可变数据
    • 合理使用状态管理库
  3. 数据处理

    • 实现请求缓存
    • 避免重复请求
    • 优化大数据渲染
    • 使用虚拟列表
  4. 代码组织

    • 合理代码分割
    • 按需加载
    • 预加载关键资源
    • 优化打包体积

小结

  1. React 性能优化的特点:

    • 组件级别优化
    • 状态管理优化
    • 渲染机制优化
    • 资源加载优化
  2. 从 Vue 到 React 的转变:

    • 理解渲染机制差异
    • 掌握优化工具
    • 建立性能意识
    • 实践优化策略
  3. 开发建议:

    • 先测量后优化
    • 避免过早优化
    • 关注用户体验
    • 持续监控改进

下一篇文章,我们将深入探讨 React 的测试策略,帮助你构建可靠的应用。

如果觉得这篇文章对你有帮助,别忘了点个赞 👍

相关推荐
坐吃山猪8 分钟前
卷积神经04-TensorFlow环境安装
人工智能·python·tensorflow
IT古董9 分钟前
【漫话机器学习系列】045.特征向量(Eigenvector)
人工智能·python·机器学习
Antonio91511 分钟前
【opencv】第8章 图像轮廓与图像分割修复
c++·人工智能·opencv·计算机视觉
Roc_z716 分钟前
Facebook 虚拟现实技术突破:沉浸式体验的前沿探索
人工智能·vr·facebook
伊一大数据&人工智能学习日志20 分钟前
OpenCV计算机视觉 08 图像的旋转
人工智能·opencv·计算机视觉
失败才是人生常态20 分钟前
《光学遥感图像中显著目标检测的多内容互补网络》2021-9
人工智能·目标检测·计算机视觉
Kai HVZ22 分钟前
《OpenCV计算机视觉实战项目》——银行卡号识别
人工智能·opencv·计算机视觉
佩淇呢23 分钟前
CSS响应式
前端·css
阿_旭26 分钟前
目标检测中的Bounding Box(边界框)介绍:定义以及不同表示方式
人工智能·目标检测·计算机视觉·检测框
马甲是掉不了一点的<.<27 分钟前
什么是卷积网络中的平移不变性?平移shft在数据增强中的意义
人工智能·深度学习·计算机视觉