Skip to content

渲染优化案例分析 - 实战性能优化示例

1. 案例1: 电商商品列表

1.1 初始问题代码

typescript
// 问题版本: 性能差
function ProductList({ products, onAddToCart }) {
  const [filter, setFilter] = useState('');
  const [sort, setSort] = useState('name');
  
  return (
    <div>
      <div>
        <input
          placeholder="搜索商品..."
          value={filter}
          onChange={(e) => setFilter(e.target.value)}
        />
        <select value={sort} onChange={(e) => setSort(e.target.value)}>
          <option value="name">按名称</option>
          <option value="price">按价格</option>
        </select>
      </div>
      
      <div className="products">
        {products
          .filter(p => p.name.toLowerCase().includes(filter.toLowerCase()))
          .sort((a, b) => {
            if (sort === 'name') return a.name.localeCompare(b.name);
            return a.price - b.price;
          })
          .map(product => (
            <div key={product.id} className="product-card">
              <img src={product.image} alt={product.name} />
              <h3>{product.name}</h3>
              <p>${product.price}</p>
              <button onClick={() => onAddToCart(product)}>
                加入购物车
              </button>
            </div>
          ))}
      </div>
    </div>
  );
}

// 性能问题:
// 1. 每次渲染都filter+sort (即使数据未变)
// 2. 每次渲染都创建新的onClick函数
// 3. products变化时重渲染所有商品卡片
// 4. 大量商品时滚动卡顿

1.2 优化步骤

typescript
// 步骤1: useMemo优化计算
function ProductListV2({ products, onAddToCart }) {
  const [filter, setFilter] = useState('');
  const [sort, setSort] = useState('name');
  
  // ✓ 缓存过滤和排序结果
  const displayProducts = useMemo(() => {
    return products
      .filter(p => p.name.toLowerCase().includes(filter.toLowerCase()))
      .sort((a, b) => {
        if (sort === 'name') return a.name.localeCompare(b.name);
        return a.price - b.price;
      });
  }, [products, filter, sort]);
  
  return (
    <div>
      <FilterControls
        filter={filter}
        onFilterChange={setFilter}
        sort={sort}
        onSortChange={setSort}
      />
      
      <div className="products">
        {displayProducts.map(product => (
          <ProductCard
            key={product.id}
            product={product}
            onAddToCart={onAddToCart}
          />
        ))}
      </div>
    </div>
  );
}

// 步骤2: 拆分组件
const FilterControls = React.memo(({ filter, onFilterChange, sort, onSortChange }) => {
  return (
    <div>
      <input
        placeholder="搜索商品..."
        value={filter}
        onChange={(e) => onFilterChange(e.target.value)}
      />
      <select value={sort} onChange={(e) => onSortChange(e.target.value)}>
        <option value="name">按名称</option>
        <option value="price">按价格</option>
      </select>
    </div>
  );
});

// 步骤3: memo商品卡片
const ProductCard = React.memo(({ product, onAddToCart }) => {
  const handleClick = useCallback(() => {
    onAddToCart(product);
  }, [product, onAddToCart]);
  
  return (
    <div className="product-card">
      <img src={product.image} alt={product.name} loading="lazy" />
      <h3>{product.name}</h3>
      <p>${product.price}</p>
      <button onClick={handleClick}>加入购物车</button>
    </div>
  );
});

// 步骤4: 虚拟滚动(商品很多时)
import { FixedSizeGrid } from 'react-window';

function VirtualProductList({ products, onAddToCart }) {
  const [filter, setFilter] = useState('');
  const [sort, setSort] = useState('name');
  
  const displayProducts = useMemo(() => {
    return products
      .filter(p => p.name.toLowerCase().includes(filter.toLowerCase()))
      .sort((a, b) => {
        if (sort === 'name') return a.name.localeCompare(b.name);
        return a.price - b.price;
      });
  }, [products, filter, sort]);
  
  const Cell = ({ columnIndex, rowIndex, style }) => {
    const index = rowIndex * 3 + columnIndex;
    if (index >= displayProducts.length) return null;
    
    const product = displayProducts[index];
    return (
      <div style={style}>
        <ProductCard product={product} onAddToCart={onAddToCart} />
      </div>
    );
  };
  
  return (
    <div>
      <FilterControls {...controlProps} />
      
      <FixedSizeGrid
        columnCount={3}
        columnWidth={200}
        height={600}
        rowCount={Math.ceil(displayProducts.length / 3)}
        rowHeight={300}
        width={620}
      >
        {Cell}
      </FixedSizeGrid>
    </div>
  );
}

// 性能提升:
// 初始渲染: 从2000ms -> 200ms (10倍)
// 过滤/排序: 从500ms -> 50ms (10倍)
// 滚动: 60fps流畅
// 内存: 减少90%

2. 案例2: 实时聊天应用

2.2 问题分析

typescript
// 问题版本
function ChatApp() {
  const [messages, setMessages] = useState([]);
  const [input, setInput] = useState('');
  const [onlineUsers, setOnlineUsers] = useState([]);
  
  // 问题1: 新消息导致整个列表重渲染
  // 问题2: input变化导致消息列表重渲染
  // 问题3: onlineUsers变化导致消息列表重渲染
  
  return (
    <div className="chat-app">
      <div className="sidebar">
        <h3>在线用户 ({onlineUsers.length})</h3>
        <ul>
          {onlineUsers.map(user => (
            <li key={user.id}>{user.name}</li>
          ))}
        </ul>
      </div>
      
      <div className="main">
        <div className="messages">
          {messages.map(msg => (
            <div key={msg.id} className="message">
              <span>{msg.user}: </span>
              <span>{msg.text}</span>
              <span>{new Date(msg.timestamp).toLocaleTimeString()}</span>
            </div>
          ))}
        </div>
        
        <div className="input-area">
          <input
            value={input}
            onChange={(e) => setInput(e.target.value)}
            onKeyPress={(e) => {
              if (e.key === 'Enter') {
                sendMessage(input);
                setInput('');
              }
            }}
          />
          <button onClick={() => {
            sendMessage(input);
            setInput('');
          }}>
            发送
          </button>
        </div>
      </div>
    </div>
  );
}

2.3 优化方案

typescript
// 优化版本
function ChatApp() {
  return (
    <div className="chat-app">
      <OnlineUsers />
      <ChatMain />
    </div>
  );
}

// 组件1: 在线用户列表
const OnlineUsers = React.memo(function OnlineUsers() {
  const [users, setUsers] = useState([]);
  
  useEffect(() => {
    const unsubscribe = subscribeToOnlineUsers(setUsers);
    return unsubscribe;
  }, []);
  
  return (
    <div className="sidebar">
      <h3>在线用户 ({users.length})</h3>
      <ul>
        {users.map(user => (
          <UserItem key={user.id} user={user} />
        ))}
      </ul>
    </div>
  );
});

const UserItem = React.memo(({ user }) => {
  return <li>{user.name}</li>;
});

// 组件2: 聊天主区域
function ChatMain() {
  return (
    <div className="main">
      <MessageList />
      <MessageInput />
    </div>
  );
}

// 组件3: 消息列表
const MessageList = React.memo(function MessageList() {
  const [messages, setMessages] = useState([]);
  const bottomRef = useRef(null);
  
  useEffect(() => {
    const unsubscribe = subscribeToMessages((newMsg) => {
      setMessages(prev => [...prev, newMsg]);
    });
    return unsubscribe;
  }, []);
  
  useEffect(() => {
    bottomRef.current?.scrollIntoView({ behavior: 'smooth' });
  }, [messages.length]);
  
  return (
    <div className="messages">
      {messages.map(msg => (
        <Message key={msg.id} message={msg} />
      ))}
      <div ref={bottomRef} />
    </div>
  );
});

const Message = React.memo(({ message }) => {
  const time = useMemo(
    () => new Date(message.timestamp).toLocaleTimeString(),
    [message.timestamp]
  );
  
  return (
    <div className="message">
      <span>{message.user}: </span>
      <span>{message.text}</span>
      <span>{time}</span>
    </div>
  );
});

// 组件4: 输入框
const MessageInput = React.memo(function MessageInput() {
  const [input, setInput] = useState('');
  
  const handleSend = useCallback(() => {
    if (input.trim()) {
      sendMessage(input);
      setInput('');
    }
  }, [input]);
  
  const handleKeyPress = useCallback((e) => {
    if (e.key === 'Enter') {
      handleSend();
    }
  }, [handleSend]);
  
  return (
    <div className="input-area">
      <input
        value={input}
        onChange={(e) => setInput(e.target.value)}
        onKeyPress={handleKeyPress}
      />
      <button onClick={handleSend}>发送</button>
    </div>
  );
});

// 性能提升:
// - input变化不影响消息列表
// - 新消息只渲染新增项
// - onlineUsers变化不影响聊天区域
// - 每个部分独立优化

3. 案例3: 数据仪表板

3.1 问题代码

typescript
function Dashboard() {
  const [data, setData] = useState(null);
  const [dateRange, setDateRange] = useState('week');
  const [loading, setLoading] = useState(true);
  
  useEffect(() => {
    setLoading(true);
    fetchDashboardData(dateRange).then(result => {
      setData(result);
      setLoading(false);
    });
  }, [dateRange]);
  
  if (loading) return <Loading />;
  
  // 问题: 所有图表在一个组件,任何更新都重渲染全部
  return (
    <div className="dashboard">
      <div className="controls">
        <button onClick={() => setDateRange('day')}></button>
        <button onClick={() => setDateRange('week')}></button>
        <button onClick={() => setDateRange('month')}></button>
      </div>
      
      <div className="charts">
        {/* 昂贵的图表组件 */}
        <LineChart data={data.sales} />
        <BarChart data={data.revenue} />
        <PieChart data={data.categories} />
        <AreaChart data={data.traffic} />
      </div>
      
      <div className="tables">
        <DataTable data={data.orders} columns={orderColumns} />
        <DataTable data={data.products} columns={productColumns} />
      </div>
    </div>
  );
}

3.2 优化方案

typescript
// 优化版本
function Dashboard() {
  const [dateRange, setDateRange] = useState('week');
  
  return (
    <div className="dashboard">
      <DateRangeControls value={dateRange} onChange={setDateRange} />
      
      <Suspense fallback={<ChartsSkeleton />}>
        <DashboardCharts dateRange={dateRange} />
      </Suspense>
      
      <Suspense fallback={<TablesSkeleton />}>
        <DashboardTables dateRange={dateRange} />
      </Suspense>
    </div>
  );
}

// 日期范围控制
const DateRangeControls = React.memo(({ value, onChange }) => {
  return (
    <div className="controls">
      <button onClick={() => onChange('day')}></button>
      <button onClick={() => onChange('week')}></button>
      <button onClick={() => onChange('month')}></button>
    </div>
  );
});

// 图表区域
function DashboardCharts({ dateRange }) {
  const data = use(fetchChartsData(dateRange));
  
  return (
    <div className="charts">
      <LazyLineChart data={data.sales} />
      <LazyBarChart data={data.revenue} />
      <LazyPieChart data={data.categories} />
      <LazyAreaChart data={data.traffic} />
    </div>
  );
}

// 懒加载图表
const LazyLineChart = lazy(() => import('./charts/LineChart'));
const LazyBarChart = lazy(() => import('./charts/BarChart'));
const LazyPieChart = lazy(() => import('./charts/PieChart'));
const LazyAreaChart = lazy(() => import('./charts/AreaChart'));

// 表格区域
function DashboardTables({ dateRange }) {
  const data = use(fetchTablesData(dateRange));
  
  return (
    <div className="tables">
      <VirtualDataTable
        data={data.orders}
        columns={orderColumns}
      />
      <VirtualDataTable
        data={data.products}
        columns={productColumns}
      />
    </div>
  );
}

// 虚拟滚动表格
const VirtualDataTable = React.memo(({ data, columns }) => {
  return (
    <FixedSizeList
      height={400}
      itemCount={data.length}
      itemSize={50}
    >
      {({ index, style }) => (
        <TableRow
          key={data[index].id}
          style={style}
          data={data[index]}
          columns={columns}
        />
      )}
    </FixedSizeList>
  );
});

// 性能提升:
// - 按需加载图表库
// - Suspense并行加载数据
// - 虚拟滚动处理大数据表格
// - 组件独立更新

4. 总结

渲染优化案例分析的核心要点:

  1. 组件拆分: 隔离更新范围
  2. useMemo: 缓存昂贵计算
  3. React.memo: 避免不必要渲染
  4. 虚拟滚动: 处理大列表
  5. 懒加载: 按需加载组件
  6. Suspense: 并行数据获取

通过实际案例掌握性能优化技巧。

5. 高级渲染优化案例

5.1 社交媒体Feed优化

jsx
// 场景:类似Twitter的无限滚动Feed
// 挑战:数以千计的帖子,包含图片、视频、互动按钮

// 优化方案
import { useInfiniteQuery } from '@tanstack/react-query';
import { useVirtual } from 'react-virtual';

function SocialFeed() {
  const parentRef = useRef();
  
  // 无限查询
  const {
    data,
    fetchNextPage,
    hasNextPage,
    isFetchingNextPage
  } = useInfiniteQuery({
    queryKey: ['feed'],
    queryFn: ({ pageParam = 0 }) => fetchFeed(pageParam),
    getNextPageParam: (lastPage) => lastPage.nextCursor
  });
  
  // 展平所有页面的帖子
  const allPosts = data?.pages.flatMap(page => page.posts) ?? [];
  
  // 虚拟滚动
  const rowVirtualizer = useVirtual({
    size: allPosts.length,
    parentRef,
    estimateSize: useCallback(() => 200, []),
    overscan: 5
  });
  
  // 监听滚动到底部
  useEffect(() => {
    const [lastItem] = [...rowVirtualizer.virtualItems].reverse();
    
    if (!lastItem) return;
    
    if (
      lastItem.index >= allPosts.length - 1 &&
      hasNextPage &&
      !isFetchingNextPage
    ) {
      fetchNextPage();
    }
  }, [
    hasNextPage,
    fetchNextPage,
    allPosts.length,
    isFetchingNextPage,
    rowVirtualizer.virtualItems
  ]);
  
  return (
    <div ref={parentRef} style={{ height: '100vh', overflow: 'auto' }}>
      <div
        style={{
          height: `${rowVirtualizer.totalSize}px`,
          position: 'relative'
        }}
      >
        {rowVirtualizer.virtualItems.map(virtualRow => {
          const post = allPosts[virtualRow.index];
          return (
            <div
              key={virtualRow.index}
              style={{
                position: 'absolute',
                top: 0,
                left: 0,
                width: '100%',
                transform: `translateY(${virtualRow.start}px)`
              }}
            >
              <PostCard post={post} />
            </div>
          );
        })}
      </div>
      {isFetchingNextPage && <LoadingSpinner />}
    </div>
  );
}

const PostCard = memo(({ post }) => {
  const [isLiked, setIsLiked] = useState(post.liked);
  const [likeCount, setLikeCount] = useState(post.likes);
  
  // 乐观更新
  const handleLike = useCallback(async () => {
    setIsLiked(prev => !prev);
    setLikeCount(prev => prev + (isLiked ? -1 : 1));
    
    try {
      await likePost(post.id);
    } catch (error) {
      // 回滚
      setIsLiked(prev => !prev);
      setLikeCount(prev => prev + (isLiked ? 1 : -1));
    }
  }, [post.id, isLiked]);
  
  return (
    <div className="post-card">
      <PostHeader author={post.author} />
      <PostContent content={post.content} />
      {post.media && <LazyMedia media={post.media} />}
      <PostActions
        isLiked={isLiked}
        likeCount={likeCount}
        onLike={handleLike}
      />
    </div>
  );
}, (prev, next) => {
  // 自定义比较:只在帖子ID变化时重渲染
  return prev.post.id === next.post.id;
});

// 性能提升:
// - 初始加载:从2s降到300ms
// - 滚动FPS:稳定60fps
// - 内存占用:控制在200MB以内
// - 支持10000+帖子流畅滚动

5.2 拖拽排序列表优化

jsx
// 场景:Trello风格的看板,支持拖拽
// 挑战:拖拽时的实时更新、大量卡片

import { DndContext, closestCenter } from '@dnd-kit/core';
import { 
  arrayMove,
  SortableContext,
  useSortable,
  verticalListSortingStrategy
} from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';

function KanbanBoard() {
  const [columns, setColumns] = useState({
    todo: [],
    inProgress: [],
    done: []
  });
  
  const handleDragEnd = useCallback((event) => {
    const { active, over } = event;
    
    if (!over || active.id === over.id) return;
    
    // 批量状态更新
    setColumns(prev => {
      // 找到源列和目标列
      const sourceColumn = findColumn(prev, active.id);
      const targetColumn = findColumn(prev, over.id);
      
      if (sourceColumn === targetColumn) {
        // 同列排序
        const oldIndex = prev[sourceColumn].findIndex(item => item.id === active.id);
        const newIndex = prev[sourceColumn].findIndex(item => item.id === over.id);
        
        return {
          ...prev,
          [sourceColumn]: arrayMove(prev[sourceColumn], oldIndex, newIndex)
        };
      } else {
        // 跨列移动
        const item = prev[sourceColumn].find(item => item.id === active.id);
        return {
          ...prev,
          [sourceColumn]: prev[sourceColumn].filter(item => item.id !== active.id),
          [targetColumn]: [...prev[targetColumn], item]
        };
      }
    });
  }, []);
  
  return (
    <DndContext collisionDetection={closestCenter} onDragEnd={handleDragEnd}>
      <div className="kanban-board">
        {Object.entries(columns).map(([columnId, items]) => (
          <Column key={columnId} id={columnId} items={items} />
        ))}
      </div>
    </DndContext>
  );
}

const Column = memo(({ id, items }) => {
  return (
    <div className="column">
      <h2>{id}</h2>
      <SortableContext
        items={items.map(item => item.id)}
        strategy={verticalListSortingStrategy}
      >
        {items.map(item => (
          <SortableCard key={item.id} item={item} />
        ))}
      </SortableContext>
    </div>
  );
});

const SortableCard = memo(({ item }) => {
  const {
    attributes,
    listeners,
    setNodeRef,
    transform,
    transition,
    isDragging
  } = useSortable({ id: item.id });
  
  const style = {
    transform: CSS.Transform.toString(transform),
    transition,
    opacity: isDragging ? 0.5 : 1
  };
  
  return (
    <div ref={setNodeRef} style={style} {...attributes} {...listeners}>
      <CardContent item={item} />
    </div>
  );
});

// 性能提升:
// - 拖拽响应:<16ms
// - 大量卡片(1000+):流畅
// - 内存:稳定

5.3 富文本编辑器优化

jsx
// 场景:类似Notion的富文本编辑器
// 挑战:实时协作、大文档、复杂格式

import { useMemo } from 'react';
import { createEditor, Transforms } from 'slate';
import { Slate, Editable, withReact } from 'slate-react';
import { withHistory } from 'slate-history';

function RichTextEditor({ initialValue, onChange }) {
  // 编辑器实例只创建一次
  const editor = useMemo(
    () => withHistory(withReact(createEditor())),
    []
  );
  
  // 渲染元素的缓存
  const renderElement = useCallback((props) => {
    switch (props.element.type) {
      case 'heading':
        return <Heading {...props} />;
      case 'list':
        return <List {...props} />;
      case 'code':
        return <CodeBlock {...props} />;
      default:
        return <Paragraph {...props} />;
    }
  }, []);
  
  // 渲染叶子节点的缓存
  const renderLeaf = useCallback((props) => {
    return <Leaf {...props} />;
  }, []);
  
  // 防抖保存
  const debouncedOnChange = useMemo(
    () => debounce((value) => onChange(value), 500),
    [onChange]
  );
  
  return (
    <Slate
      editor={editor}
      value={initialValue}
      onChange={debouncedOnChange}
    >
      <Toolbar editor={editor} />
      <Editable
        renderElement={renderElement}
        renderLeaf={renderLeaf}
        placeholder="开始输入..."
        spellCheck
        autoFocus
      />
    </Slate>
  );
}

// 优化的元素组件
const Heading = memo(({ attributes, children, element }) => {
  const level = element.level || 1;
  const Tag = `h${level}`;
  return <Tag {...attributes}>{children}</Tag>;
}, (prev, next) => {
  return (
    prev.element.level === next.element.level &&
    prev.children === next.children
  );
});

const CodeBlock = memo(({ attributes, children }) => {
  return (
    <pre {...attributes}>
      <code>{children}</code>
    </pre>
  );
});

const Leaf = memo(({ attributes, children, leaf }) => {
  let content = children;
  
  if (leaf.bold) {
    content = <strong>{content}</strong>;
  }
  
  if (leaf.italic) {
    content = <em>{content}</em>;
  }
  
  if (leaf.code) {
    content = <code>{content}</code>;
  }
  
  return <span {...attributes}>{content}</span>;
});

// 性能提升:
// - 输入延迟:<10ms
// - 大文档(10000+行):流畅编辑
// - 内存:稳定增长

6. React 19特性优化案例

6.1 使用Compiler自动优化

jsx
// 优化前:手动添加优化
function ProductFilters({ products, onFilterChange }) {
  const [category, setCategory] = useState('all');
  const [priceRange, setPriceRange] = useState([0, 1000]);
  const [inStock, setInStock] = useState(false);
  
  const filteredProducts = useMemo(() => {
    return products.filter(product => {
      if (category !== 'all' && product.category !== category) {
        return false;
      }
      if (product.price < priceRange[0] || product.price > priceRange[1]) {
        return false;
      }
      if (inStock && !product.inStock) {
        return false;
      }
      return true;
    });
  }, [products, category, priceRange, inStock]);
  
  const handleCategoryChange = useCallback((e) => {
    setCategory(e.target.value);
  }, []);
  
  const handlePriceChange = useCallback((range) => {
    setPriceRange(range);
  }, []);
  
  useEffect(() => {
    onFilterChange(filteredProducts);
  }, [filteredProducts, onFilterChange]);
  
  return (
    <div>
      {/* 过滤器UI */}
    </div>
  );
}

// 优化后:React Compiler自动优化
function ProductFilters({ products, onFilterChange }) {
  const [category, setCategory] = useState('all');
  const [priceRange, setPriceRange] = useState([0, 1000]);
  const [inStock, setInStock] = useState(false);
  
  // Compiler自动memoize
  const filteredProducts = products.filter(product => {
    if (category !== 'all' && product.category !== category) {
      return false;
    }
    if (product.price < priceRange[0] || product.price > priceRange[1]) {
      return false;
    }
    if (inStock && !product.inStock) {
      return false;
    }
    return true;
  });
  
  // Compiler自动优化handlers
  const handleCategoryChange = (e) => {
    setCategory(e.target.value);
  };
  
  const handlePriceChange = (range) => {
    setPriceRange(range);
  };
  
  useEffect(() => {
    onFilterChange(filteredProducts);
  }, [filteredProducts, onFilterChange]);
  
  return (
    <div>
      {/* 过滤器UI */}
    </div>
  );
}

// 代码简化:减少30%样板代码
// 性能:与手动优化相同

6.2 使用use()Hook优化数据加载

jsx
// 优化前:传统方式
function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [posts, setPosts] = useState([]);
  const [loading, setLoading] = useState(true);
  
  useEffect(() => {
    async function loadData() {
      setLoading(true);
      try {
        const userData = await fetchUser(userId);
        setUser(userData);
        
        const postsData = await fetchUserPosts(userId);
        setPosts(postsData);
      } finally {
        setLoading(false);
      }
    }
    loadData();
  }, [userId]);
  
  if (loading) return <Loading />;
  
  return (
    <div>
      <UserInfo user={user} />
      <PostList posts={posts} />
    </div>
  );
}

// 优化后:use() + Suspense
function UserProfile({ userId }) {
  // 并行加载
  const user = use(fetchUser(userId));
  const posts = use(fetchUserPosts(userId));
  
  return (
    <div>
      <UserInfo user={user} />
      <PostList posts={posts} />
    </div>
  );
}

// 父组件使用Suspense
function ProfilePage({ userId }) {
  return (
    <Suspense fallback={<ProfileSkeleton />}>
      <UserProfile userId={userId} />
    </Suspense>
  );
}

// 性能提升:
// - 代码简化:减少50%状态管理代码
// - 并行加载:自动并发
// - 错误处理:ErrorBoundary统一处理

6.3 Server Components优化

jsx
// app/dashboard/page.tsx (Server Component)
async function DashboardPage() {
  // 在服务器直接获取数据
  const stats = await getStats();
  const recentOrders = await getRecentOrders();
  const topProducts = await getTopProducts();
  
  return (
    <div className="dashboard">
      <h1>仪表盘</h1>
      
      <div className="stats-grid">
        {stats.map(stat => (
          <StatCard key={stat.id} stat={stat} />
        ))}
      </div>
      
      <div className="dashboard-content">
        {/* Server Component */}
        <RecentOrders orders={recentOrders} />
        
        {/* Client Component用于交互 */}
        <TopProductsChart data={topProducts} />
      </div>
    </div>
  );
}

// Server Component:无需客户端JS
function RecentOrders({ orders }) {
  return (
    <div className="orders">
      <h2>最近订单</h2>
      <table>
        {orders.map(order => (
          <tr key={order.id}>
            <td>{order.id}</td>
            <td>{order.customer}</td>
            <td>{order.total}</td>
          </tr>
        ))}
      </table>
    </div>
  );
}

// Client Component:需要交互
'use client';
function TopProductsChart({ data }) {
  return (
    <div className="chart">
      <h2>热销产品</h2>
      <Chart data={data} />
    </div>
  );
}

// 性能提升:
// - Bundle减少:Server Component不打包到客户端
// - SEO友好:服务器渲染,完整HTML
// - 加载速度:减少70% JavaScript
// - 数据获取:服务器直连数据库,更快

7. 性能监控与持续优化

7.1 自动化性能测试

javascript
// performance.test.js
import { render, screen } from '@testing-library/react';
import { measureRenderTime } from './test-utils';

describe('ProductList Performance', () => {
  it('应该在100ms内渲染1000个产品', async () => {
    const products = generateProducts(1000);
    
    const renderTime = await measureRenderTime(() => {
      render(<ProductList products={products} />);
    });
    
    expect(renderTime).toBeLessThan(100);
  });
  
  it('滚动应该维持60fps', async () => {
    const products = generateProducts(10000);
    render(<ProductList products={products} />);
    
    const fps = await measureScrollFPS();
    expect(fps).toBeGreaterThan(55); // 允许小波动
  });
});

// 性能测试工具
function measureRenderTime(renderFn) {
  return new Promise((resolve) => {
    const start = performance.now();
    renderFn();
    
    requestIdleCallback(() => {
      const end = performance.now();
      resolve(end - start);
    });
  });
}

7.2 生产环境监控

jsx
// 性能监控组件
function PerformanceMonitor({ children }) {
  useEffect(() => {
    // 监控长任务
    const observer = new PerformanceObserver((list) => {
      for (const entry of list.getEntries()) {
        if (entry.duration > 50) {
          // 上报长任务
          reportMetric({
            type: 'long-task',
            duration: entry.duration,
            startTime: entry.startTime
          });
        }
      }
    });
    
    observer.observe({ entryTypes: ['longtask'] });
    
    return () => observer.disconnect();
  }, []);
  
  useEffect(() => {
    // 监控内存
    const checkMemory = () => {
      if (performance.memory) {
        const usage = performance.memory.usedJSHeapSize;
        const limit = performance.memory.jsHeapSizeLimit;
        const percentage = (usage / limit) * 100;
        
        if (percentage > 90) {
          reportMetric({
            type: 'memory-warning',
            usage,
            percentage
          });
        }
      }
    };
    
    const intervalId = setInterval(checkMemory, 30000);
    return () => clearInterval(intervalId);
  }, []);
  
  return children;
}

7.3 性能预算

javascript
// performance-budget.config.js
module.exports = {
  budgets: [
    {
      name: 'JavaScript',
      limit: '300kb',
      threshold: 0.9
    },
    {
      name: 'CSS',
      limit: '50kb',
      threshold: 0.9
    },
    {
      name: 'Images',
      limit: '500kb',
      threshold: 0.9
    },
    {
      name: 'FCP',
      limit: '1.5s',
      threshold: 0.9
    },
    {
      name: 'LCP',
      limit: '2.5s',
      threshold: 0.9
    }
  ]
};

// CI/CD中的性能检查
const checkPerformanceBudget = async () => {
  const report = await runLighthouse();
  const budgets = require('./performance-budget.config');
  
  let passed = true;
  
  for (const budget of budgets.budgets) {
    const actual = report.metrics[budget.name];
    const limit = parseSize(budget.limit);
    
    if (actual > limit) {
      console.error(`❌ ${budget.name}: ${actual} exceeds limit ${budget.limit}`);
      passed = false;
    } else {
      console.log(`✅ ${budget.name}: ${actual} within limit ${budget.limit}`);
    }
  }
  
  if (!passed) {
    process.exit(1);
  }
};

8. 面试重点

8.1 渲染优化面试题

typescript
const interviewQuestions = [
  {
    q: '如何优化一个包含10000条数据的列表?',
    a: `
      1. 虚拟滚动:只渲染可见项(react-window)
      2. 分页:每页显示50-100条
      3. 无限滚动:逐步加载
      4. React.memo:避免列表项重渲染
      5. key优化:使用稳定的唯一ID
    `
  },
  
  {
    q: '拖拽功能如何优化性能?',
    a: `
      1. 使用transform代替top/left
      2. 降低更新频率(throttle)
      3. 使用will-change提示浏览器
      4. 减少拖拽时的DOM操作
      5. 使用专业库(dnd-kit、react-dnd)
    `
  },
  
  {
    q: 'React 19的Compiler如何提升性能?',
    a: `
      1. 自动分析依赖
      2. 自动插入memoization
      3. 减少手动优化代码
      4. 编译时优化,无运行时开销
      5. 性能接近手动优化
    `
  },
  
  {
    q: 'Server Components的性能优势?',
    a: `
      1. 减少客户端Bundle体积
      2. 服务器直连数据库,更快
      3. SEO友好,完整HTML
      4. 自动代码分割
      5. 流式渲染,渐进式加载
    `
  },
  
  {
    q: '如何监控React应用的性能?',
    a: `
      1. React DevTools Profiler
      2. Chrome Performance面板
      3. Web Vitals(LCP, FID, CLS)
      4. PerformanceObserver API
      5. 第三方监控(Sentry, LogRocket)
    `
  }
];

8.2 优化检查清单

typescript
const renderOptimizationChecklist = {
  '组件层面': [
    '使用React.memo包裹纯展示组件',
    '合理拆分组件,控制更新范围',
    '避免在render中创建新对象/数组',
    '使用key优化列表渲染',
    '考虑使用React Compiler'
  ],
  
  'Hook层面': [
    '使用useMemo缓存昂贵计算',
    '使用useCallback稳定函数引用',
    '避免useEffect中的不必要依赖',
    '考虑useTransition延迟更新'
  ],
  
  '大列表': [
    '虚拟滚动(react-window)',
    '分页或无限滚动',
    '图片懒加载',
    '骨架屏提升体验'
  ],
  
  '数据加载': [
    '使用Suspense并行加载',
    '考虑Server Components',
    '实现数据预取',
    '合理的错误边界'
  ],
  
  '监控': [
    '设置性能预算',
    '监控Core Web Vitals',
    '使用Profiler API',
    'CI/CD性能检查'
  ]
};