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

原创
01/12 20:50
阅读数 63

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

渲染优化对比

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 默认采用自上而下的渲染策略,父组件更新会触发子组件重渲染:

function App() {
  const [count, setCount] = useState(0);
  
  return (
    <div>
      <h1>标题</h1>
      <p>描述</p>
      {/* 每次 count 变化,整个组件树都会重新渲染 */}
      <div>点击次数:{count}</div>
      <button onclick="{()" => setCount(count + 1)}&gt;+1</button>
    </div>
  );
}

优化后的版本:

const Title = memo(function Title() {
  return <h1>标题</h1>;
});

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

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

function App() {
  const [count, setCount] = useState(0);
  const increment = useCallback(() =&gt; {
    setCount(c =&gt; c + 1);
  }, []);
  
  return (
    <div>
      <title></title>
      <description />
      <counter count="{count}" onIncrement="{increment}" />
    </div>
  );
}

组件优化策略

1. 组件拆分与记忆化

// 不好的实践
function ProductList({ products, onSelect }) {
  return (
    <div>
      {products.map(product =&gt; (
        <div key="{product.id}" onclick="{()" => onSelect(product)}&gt;
          <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(() =&gt; {
    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 =&gt; (
        <productitem key="{product.id}" product="{product}" onSelect="{onSelect}" />
      ))}
    </div>
  );
}

2. 状态管理优化

// 不好的实践
function Dashboard() {
  const [state, setState] = useState({
    user: null,
    products: [],
    orders: [],
    settings: {}
  });
  
  // 任何状态更新都会导致整个组件重渲染
  const updateUser = (user) =&gt; {
    setState(prev =&gt; ({ ...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. 计算属性优化

// 不好的实践
function OrderSummary({ orders }) {
  // 每次渲染都会重新计算
  const totalAmount = orders.reduce((sum, order) =&gt; sum + order.amount, 0);
  const completedOrders = orders.filter(order =&gt; order.status === 'completed');
  const pendingOrders = orders.filter(order =&gt; order.status === 'pending');
  
  return (
    <div>
      <p>总金额:{totalAmount}</p>
      <p>已完成订单:{completedOrders.length}</p>
      <p>待处理订单:{pendingOrders.length}</p>
    </div>
  );
}

// 好的实践
function OrderSummary({ orders }) {
  const totalAmount = useMemo(() =&gt; {
    return orders.reduce((sum, order) =&gt; sum + order.amount, 0);
  }, [orders]);
  
  const { completedOrders, pendingOrders } = useMemo(() =&gt; {
    return {
      completedOrders: orders.filter(order =&gt; order.status === 'completed'),
      pendingOrders: orders.filter(order =&gt; order.status === 'pending')
    };
  }, [orders]);
  
  return (
    <div>
      <p>总金额:{totalAmount}</p>
      <p>已完成订单:{completedOrders.length}</p>
      <p>待处理订单:{pendingOrders.length}</p>
    </div>
  );
}

列表渲染优化

1. 虚拟列表

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(() =&gt; {
    return items.slice(startIndex, endIndex).map((item, index) =&gt; ({
      ...item,
      index: startIndex + index
    }));
  }, [items, startIndex, endIndex]);
  
  const handleScroll = useCallback((e) =&gt; {
    setScrollTop(e.target.scrollTop);
  }, []);
  
  return (
    <div ref="{containerRef}" style="{{" height: windowheight, overflow: 'auto' }} onscroll="{handleScroll}">
      <div style="{{" height: totalheight, position: 'relative' }}>
        {visibleItems.map(item =&gt; (
          <div key="{item.id}" style="{{" position: 'absolute', top: item.index * itemheight, height: itemheight }}>
            {item.content}
          </div>
        ))}
      </div>
    </div>
  );
}

2. 无限滚动

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 () =&gt; {
    if (loading || !hasMore) return;
    
    setLoading(true);
    try {
      const newItems = await fetchItems(page);
      if (newItems.length === 0) {
        setHasMore(false);
      } else {
        setItems(prev =&gt; [...prev, ...newItems]);
        setPage(p =&gt; p + 1);
      }
    } finally {
      setLoading(false);
    }
  }, [fetchItems, page, loading, hasMore]);
  
  useEffect(() =&gt; {
    const container = containerRef.current;
    if (!container) return;
    
    const observer = new IntersectionObserver(
      entries =&gt; {
        if (entries[0].isIntersecting) {
          loadMore();
        }
      },
      { threshold: 0.5 }
    );
    
    const sentinel = container.lastElementChild;
    if (sentinel) {
      observer.observe(sentinel);
    }
    
    return () =&gt; observer.disconnect();
  }, [loadMore]);
  
  return (
    <div ref="{containerRef}" style="{{" height: '100vh', overflow: 'auto' }}>
      {items.map(item =&gt; (
        <div key="{item.id}" style="{{" height: itemheight }}>
          {item.content}
        </div>
      ))}
      {hasMore &amp;&amp; (
        <div style="{{" height: itemheight, textalign: 'center' }}>
          {loading ? '加载中...' : '向下滚动加载更多'}
        </div>
      )}
    </div>
  );
}

数据获取优化

1. 请求缓存

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(() =&gt; {
    const fetchData = async () =&gt; {
      if (cache.current.has(key) &amp;&amp; !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. 请求去重

function useDedupeQuery(key, fetcher) {
  const pendingRequests = useRef(new Map());
  
  const executeQuery = useCallback(async () =&gt; {
    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. 路由级别分割

const Dashboard = lazy(() =&gt; import('./pages/Dashboard'));
const Profile = lazy(() =&gt; import('./pages/Profile'));
const Settings = lazy(() =&gt; import('./pages/Settings'));

function App() {
  return (
    <suspense fallback="{<Loading" />}&gt;
      <routes>
        <route path="/" element="{<Dashboard" />} /&gt;
        <route path="/profile" element="{<Profile" />} /&gt;
        <route path="/settings" element="{<Settings" />} /&gt;
      </routes>
    
  );
}

2. 组件级别分割

const HeavyChart = lazy(() =&gt; import('./components/HeavyChart'));

function Dashboard() {
  const [showChart, setShowChart] = useState(false);
  
  return (
    <div>
      <button onclick="{()" => setShowChart(true)}&gt;显示图表</button>
      {showChart &amp;&amp; (
        <suspense fallback="{<Loading" />}&gt;
          <heavychart />
        
      )}
    </div>
  );
}

工具和监控

1. 性能分析

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. 性能监控

function usePerformanceMonitor() {
  useEffect(() =&gt; {
    const observer = new PerformanceObserver((list) =&gt; {
      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 () =&gt; observer.disconnect();
  }, []);
}

最佳实践

  1. 渲染优化

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

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

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

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

小结

  1. React 性能优化的特点:

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

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

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

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

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

展开阅读全文
加载中
点击引领话题📣 发布并加入讨论🔥
0 评论
0 收藏
0
分享
返回顶部
顶部