Skip to content

渲染性能问题排查 - React应用性能优化实战

1. 渲染性能基础

1.1 React渲染流程

typescript
const renderingProcess = {
  阶段: [
    '1. Render阶段: 计算需要更新的内容',
    '2. Commit阶段: 实际更新DOM',
    '3. Layout阶段: 浏览器布局',
    '4. Paint阶段: 浏览器绘制'
  ],
  React工作流: [
    '触发更新 (setState, props变化)',
    '调用render函数',
    'Diff算法比较',
    '生成Fiber树',
    '提交更新到DOM',
    '执行副作用'
  ],
  性能瓶颈: [
    '不必要的重渲染',
    '复杂的render函数',
    '大列表渲染',
    'Context滥用',
    '昂贵的计算',
    '过多的DOM操作'
  ]
};

1.2 性能指标

javascript
// 关键性能指标
const performanceMetrics = {
  FCP: {
    名称: 'First Contentful Paint',
    定义: '首次内容绘制时间',
    目标: '< 1.8s'
  },
  LCP: {
    名称: 'Largest Contentful Paint',
    定义: '最大内容绘制时间',
    目标: '< 2.5s'
  },
  FID: {
    名称: 'First Input Delay',
    定义: '首次输入延迟',
    目标: '< 100ms'
  },
  CLS: {
    名称: 'Cumulative Layout Shift',
    定义: '累积布局偏移',
    目标: '< 0.1'
  },
  TTI: {
    名称: 'Time to Interactive',
    定义: '可交互时间',
    目标: '< 3.8s'
  },
  TBT: {
    名称: 'Total Blocking Time',
    定义: '总阻塞时间',
    目标: '< 300ms'
  }
};

// 测量自定义指标
function measurePerformance() {
  // React组件渲染时间
  const start = performance.now();
  
  // 渲染组件...
  
  const end = performance.now();
  console.log(`Render time: ${end - start}ms`);
}

2. 识别性能问题

2.1 React DevTools Profiler

jsx
// 使用Profiler API
import { Profiler } from 'react';

function App() {
  return (
    <Profiler id="App" onRender={onRenderCallback}>
      <MainContent />
    </Profiler>
  );
}

function onRenderCallback(
  id,                    // Profiler的id
  phase,                 // "mount" 或 "update"
  actualDuration,        // 本次渲染花费的时间
  baseDuration,          // 未优化情况下渲染所需时间
  startTime,             // React开始渲染的时间
  commitTime,            // React提交更新的时间
  interactions           // 触发此次渲染的交互集合
) {
  console.log(`[Profiler] ${id} - ${phase}:`, {
    actualDuration: `${actualDuration.toFixed(2)}ms`,
    baseDuration: `${baseDuration.toFixed(2)}ms`,
    efficiency: `${((1 - actualDuration / baseDuration) * 100).toFixed(2)}%`
  });
  
  // 记录慢渲染
  if (actualDuration > 16) {  // > 16ms会掉帧
    console.warn(`[Slow Render] ${id} took ${actualDuration.toFixed(2)}ms`);
  }
}

// 有条件地启用Profiler
function ConditionalProfiler({ children, enabled = process.env.NODE_ENV === 'development' }) {
  if (enabled) {
    return (
      <Profiler id="ConditionalProfiler" onRender={onRenderCallback}>
        {children}
      </Profiler>
    );
  }
  return children;
}

2.2 Chrome DevTools Performance

typescript
const chromePerformanceGuide = {
  使用步骤: [
    '1. 打开Chrome DevTools -> Performance',
    '2. 点击Record',
    '3. 执行操作(如滚动、点击)',
    '4. 停止录制',
    '5. 分析火焰图'
  ],
  关键区域: [
    'Main线程: JavaScript执行和DOM操作',
    'Compositor: 合成器线程',
    'Raster: 光栅化',
    'GPU: GPU操作'
  ],
  识别问题: [
    '长任务 (Long Tasks): 黄色块 > 50ms',
    '强制同步布局 (Forced Reflow)',
    'JavaScript执行时间过长',
    '过多的样式计算',
    '过多的布局重排'
  ]
};

2.3 自定义性能监控

jsx
// 创建性能监控Hook
function usePerformanceMonitor(componentName, threshold = 16) {
  const renderCountRef = useRef(0);
  const slowRendersRef = useRef(0);
  const renderTimesRef = useRef([]);
  
  const startTime = useRef(performance.now());
  
  useEffect(() => {
    const endTime = performance.now();
    const renderTime = endTime - startTime.current;
    
    renderCountRef.current++;
    renderTimesRef.current.push(renderTime);
    
    if (renderTime > threshold) {
      slowRendersRef.current++;
      console.warn(
        `[Slow Render] ${componentName}: ${renderTime.toFixed(2)}ms` +
        ` (${slowRendersRef.current}/${renderCountRef.current})`
      );
    }
    
    // 每10次渲染报告一次统计
    if (renderCountRef.current % 10 === 0) {
      const times = renderTimesRef.current;
      const avg = times.reduce((a, b) => a + b, 0) / times.length;
      const max = Math.max(...times);
      const min = Math.min(...times);
      
      console.log(`[Performance] ${componentName}:`, {
        renders: renderCountRef.current,
        slowRenders: slowRendersRef.current,
        avgTime: `${avg.toFixed(2)}ms`,
        maxTime: `${max.toFixed(2)}ms`,
        minTime: `${min.toFixed(2)}ms`
      });
    }
    
    startTime.current = performance.now();
  });
  
  return {
    renderCount: renderCountRef.current,
    slowRenders: slowRendersRef.current
  };
}

// 使用
function MonitoredComponent() {
  const { renderCount, slowRenders } = usePerformanceMonitor('MonitoredComponent');
  const [count, setCount] = useState(0);
  
  // 模拟昂贵计算
  const expensiveValue = useMemo(() => {
    let result = 0;
    for (let i = 0; i < 1000000; i++) {
      result += i;
    }
    return result;
  }, [count]);
  
  return (
    <div>
      <p>Count: {count}</p>
      <p>Expensive Value: {expensiveValue}</p>
      <p>Renders: {renderCount} (Slow: {slowRenders})</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

2.4 Why Did You Render

jsx
// 安装: npm install @welldone-software/why-did-you-render

// 配置 (在index.js最顶部)
import React from 'react';

if (process.env.NODE_ENV === 'development') {
  const whyDidYouRender = require('@welldone-software/why-did-you-render');
  whyDidYouRender(React, {
    trackAllPureComponents: true,
    trackHooks: true,
    trackExtraHooks: [[require('react-redux/lib'), 'useSelector']],
    logOnDifferentValues: true
  });
}

// 使用: 在组件上添加标记
function MyComponent({ user }) {
  return <div>{user.name}</div>;
}

MyComponent.whyDidYouRender = true;

// 或使用Hook
function useWhyDidYouUpdate(name, props) {
  const previousProps = useRef();
  
  useEffect(() => {
    if (previousProps.current) {
      const allKeys = Object.keys({ ...previousProps.current, ...props });
      const changedProps = {};
      
      allKeys.forEach(key => {
        if (previousProps.current[key] !== props[key]) {
          changedProps[key] = {
            from: previousProps.current[key],
            to: props[key]
          };
        }
      });
      
      if (Object.keys(changedProps).length > 0) {
        console.log('[Why Update]', name, changedProps);
      }
    }
    
    previousProps.current = props;
  });
}

// 使用
function Component({ user, settings }) {
  useWhyDidYouUpdate('Component', { user, settings });
  return <div>{user.name}</div>;
}

3. 常见性能问题

3.1 不必要的重渲染

jsx
// ❌ 问题: 父组件重渲染导致子组件重渲染
function Parent() {
  const [count, setCount] = useState(0);
  const [text, setText] = useState('');
  
  return (
    <div>
      <input value={text} onChange={e => setText(e.target.value)} />
      <button onClick={() => setCount(count + 1)}>Count: {count}</button>
      {/* text变化时,Child也会重渲染 */}
      <ExpensiveChild count={count} />
    </div>
  );
}

function ExpensiveChild({ count }) {
  console.log('ExpensiveChild rendered');
  
  // 模拟昂贵渲染
  const result = useMemo(() => {
    let sum = 0;
    for (let i = 0; i < 1000000; i++) {
      sum += i;
    }
    return sum;
  }, []);
  
  return <div>Count: {count}, Result: {result}</div>;
}

// ✅ 解决方案1: React.memo
const MemoizedChild = React.memo(function ExpensiveChild({ count }) {
  console.log('MemoizedChild rendered');
  
  const result = useMemo(() => {
    let sum = 0;
    for (let i = 0; i < 1000000; i++) {
      sum += i;
    }
    return sum;
  }, []);
  
  return <div>Count: {count}, Result: {result}</div>;
});

function ParentSolution1() {
  const [count, setCount] = useState(0);
  const [text, setText] = useState('');
  
  return (
    <div>
      <input value={text} onChange={e => setText(e.target.value)} />
      <button onClick={() => setCount(count + 1)}>Count: {count}</button>
      {/* text变化时,MemoizedChild不会重渲染 */}
      <MemoizedChild count={count} />
    </div>
  );
}

// ✅ 解决方案2: 状态下放
function ParentSolution2() {
  const [count, setCount] = useState(0);
  
  return (
    <div>
      <TextInput />  {/* 独立组件,不影响其他组件 */}
      <button onClick={() => setCount(count + 1)}>Count: {count}</button>
      <ExpensiveChild count={count} />
    </div>
  );
}

function TextInput() {
  const [text, setText] = useState('');
  return <input value={text} onChange={e => setText(e.target.value)} />;
}

// ✅ 解决方案3: children prop
function ParentSolution3({ children }) {
  const [text, setText] = useState('');
  
  return (
    <div>
      <input value={text} onChange={e => setText(e.target.value)} />
      {/* children不会因为text变化而重渲染 */}
      {children}
    </div>
  );
}

function App() {
  const [count, setCount] = useState(0);
  
  return (
    <ParentSolution3>
      <button onClick={() => setCount(count + 1)}>Count: {count}</button>
      <ExpensiveChild count={count} />
    </ParentSolution3>
  );
}

3.2 内联对象和数组

jsx
// ❌ 问题: 每次渲染都创建新对象
function BadInlineObjects() {
  const [count, setCount] = useState(0);
  
  return (
    <div>
      <button onClick={() => setCount(count + 1)}>Increment</button>
      {/* style对象每次都是新的,导致不必要的重渲染 */}
      <ExpensiveComponent 
        style={{ color: 'red', fontSize: 16 }}
        options={['option1', 'option2']}
      />
    </div>
  );
}

// ✅ 解决方案1: 提取到组件外部
const STYLES = { color: 'red', fontSize: 16 };
const OPTIONS = ['option1', 'option2'];

function GoodInlineObjectsSolution1() {
  const [count, setCount] = useState(0);
  
  return (
    <div>
      <button onClick={() => setCount(count + 1)}>Increment</button>
      <ExpensiveComponent style={STYLES} options={OPTIONS} />
    </div>
  );
}

// ✅ 解决方案2: useMemo
function GoodInlineObjectsSolution2() {
  const [count, setCount] = useState(0);
  
  const style = useMemo(() => ({ color: 'red', fontSize: 16 }), []);
  const options = useMemo(() => ['option1', 'option2'], []);
  
  return (
    <div>
      <button onClick={() => setCount(count + 1)}>Increment</button>
      <ExpensiveComponent style={style} options={options} />
    </div>
  );
}

// ✅ 解决方案3: 自定义比较
const ExpensiveComponent = React.memo(
  function({ style, options }) {
    console.log('ExpensiveComponent rendered');
    return <div style={style}>{options.join(', ')}</div>;
  },
  (prevProps, nextProps) => {
    // 自定义比较逻辑
    return (
      prevProps.style.color === nextProps.style.color &&
      prevProps.style.fontSize === nextProps.style.fontSize &&
      prevProps.options.length === nextProps.options.length &&
      prevProps.options.every((opt, i) => opt === nextProps.options[i])
    );
  }
);

3.3 内联函数

jsx
// ❌ 问题: 每次渲染都创建新函数
function BadInlineFunctions() {
  const [items, setItems] = useState([1, 2, 3]);
  
  return (
    <div>
      {items.map(item => (
        /* 每个item的onClick都是新函数 */
        <ExpensiveItem
          key={item}
          item={item}
          onClick={() => console.log(item)}
        />
      ))}
    </div>
  );
}

// ✅ 解决方案1: useCallback
function GoodInlineFunctionsSolution1() {
  const [items, setItems] = useState([1, 2, 3]);
  
  const handleClick = useCallback((item) => {
    console.log(item);
  }, []);
  
  return (
    <div>
      {items.map(item => (
        <ExpensiveItem
          key={item}
          item={item}
          onClick={handleClick}
        />
      ))}
    </div>
  );
}

// ExpensiveItem需要处理参数
const ExpensiveItem = React.memo(function({ item, onClick }) {
  console.log('ExpensiveItem rendered:', item);
  return <div onClick={() => onClick(item)}>{item}</div>;
});

// ✅ 解决方案2: 将状态下放到子组件
function GoodInlineFunctionsSolution2() {
  const [items, setItems] = useState([1, 2, 3]);
  
  return (
    <div>
      {items.map(item => (
        <SelfContainedItem key={item} item={item} />
      ))}
    </div>
  );
}

function SelfContainedItem({ item }) {
  const handleClick = useCallback(() => {
    console.log(item);
  }, [item]);
  
  return <div onClick={handleClick}>{item}</div>;
}

3.4 Context滥用

jsx
// ❌ 问题: Context value每次都是新对象
const AppContext = createContext();

function BadContextProvider({ children }) {
  const [user, setUser] = useState({ name: 'John' });
  const [theme, setTheme] = useState('light');
  
  // value对象每次渲染都是新的!
  return (
    <AppContext.Provider value={{ user, setUser, theme, setTheme }}>
      {children}
    </AppContext.Provider>
  );
}

// ✅ 解决方案1: useMemo缓存value
function GoodContextProviderSolution1({ children }) {
  const [user, setUser] = useState({ name: 'John' });
  const [theme, setTheme] = useState('light');
  
  const value = useMemo(() => ({
    user,
    setUser,
    theme,
    setTheme
  }), [user, theme]);
  
  return (
    <AppContext.Provider value={value}>
      {children}
    </AppContext.Provider>
  );
}

// ✅ 解决方案2: 分离Context
const UserContext = createContext();
const ThemeContext = createContext();

function GoodContextProviderSolution2({ children }) {
  const [user, setUser] = useState({ name: 'John' });
  const [theme, setTheme] = useState('light');
  
  const userValue = useMemo(() => ({ user, setUser }), [user]);
  const themeValue = useMemo(() => ({ theme, setTheme }), [theme]);
  
  return (
    <UserContext.Provider value={userValue}>
      <ThemeContext.Provider value={themeValue}>
        {children}
      </ThemeContext.Provider>
    </UserContext.Provider>
  );
}

// 消费者只订阅需要的Context
function UserDisplay() {
  const { user } = useContext(UserContext);  // 只在user变化时重渲染
  return <div>{user.name}</div>;
}

function ThemeDisplay() {
  const { theme } = useContext(ThemeContext);  // 只在theme变化时重渲染
  return <div>Theme: {theme}</div>;
}

// ✅ 解决方案3: Context选择器
function createContextSelector(Context) {
  return function useContextSelector(selector) {
    const value = useContext(Context);
    const selectedValue = selector(value);
    const selectedRef = useRef(selectedValue);
    
    if (selectedRef.current !== selectedValue) {
      selectedRef.current = selectedValue;
    }
    
    return selectedRef.current;
  };
}

const useAppContextSelector = createContextSelector(AppContext);

function OptimizedComponent() {
  // 只在user.name变化时重渲染
  const userName = useAppContextSelector(state => state.user.name);
  return <div>{userName}</div>;
}

4. 大列表优化

4.1 虚拟滚动

jsx
// 简单的虚拟列表实现
function VirtualList({ items, itemHeight, containerHeight }) {
  const [scrollTop, setScrollTop] = useState(0);
  
  const totalHeight = items.length * itemHeight;
  const startIndex = Math.floor(scrollTop / itemHeight);
  const endIndex = Math.min(
    startIndex + Math.ceil(containerHeight / itemHeight) + 1,
    items.length
  );
  
  const visibleItems = items.slice(startIndex, endIndex);
  const offsetY = startIndex * itemHeight;
  
  const handleScroll = (e) => {
    setScrollTop(e.target.scrollTop);
  };
  
  return (
    <div
      style={{ height: containerHeight, overflow: 'auto' }}
      onScroll={handleScroll}
    >
      <div style={{ height: totalHeight, position: 'relative' }}>
        <div style={{ transform: `translateY(${offsetY}px)` }}>
          {visibleItems.map((item, index) => (
            <div
              key={startIndex + index}
              style={{ height: itemHeight }}
            >
              {item}
            </div>
          ))}
        </div>
      </div>
    </div>
  );
}

// 使用react-window库 (推荐)
import { FixedSizeList } from 'react-window';

function OptimizedVirtualList({ items }) {
  const Row = ({ index, style }) => (
    <div style={style}>
      {items[index]}
    </div>
  );
  
  return (
    <FixedSizeList
      height={600}
      itemCount={items.length}
      itemSize={50}
      width="100%"
    >
      {Row}
    </FixedSizeList>
  );
}

// 动态高度虚拟列表
import { VariableSizeList } from 'react-window';

function DynamicVirtualList({ items }) {
  const listRef = useRef();
  const rowHeights = useRef({});
  
  const getItemSize = (index) => {
    return rowHeights.current[index] || 50;
  };
  
  const setRowHeight = (index, size) => {
    listRef.current.resetAfterIndex(0);
    rowHeights.current = { ...rowHeights.current, [index]: size };
  };
  
  const Row = ({ index, style }) => {
    const rowRef = useRef();
    
    useEffect(() => {
      if (rowRef.current) {
        setRowHeight(index, rowRef.current.clientHeight);
      }
    }, [index]);
    
    return (
      <div style={style}>
        <div ref={rowRef}>
          {items[index]}
        </div>
      </div>
    );
  };
  
  return (
    <VariableSizeList
      ref={listRef}
      height={600}
      itemCount={items.length}
      itemSize={getItemSize}
      width="100%"
    >
      {Row}
    </VariableSizeList>
  );
}

4.2 分页和无限滚动

jsx
// 分页
function PaginatedList({ items, pageSize = 20 }) {
  const [currentPage, setCurrentPage] = useState(1);
  
  const totalPages = Math.ceil(items.length / pageSize);
  const startIndex = (currentPage - 1) * pageSize;
  const endIndex = startIndex + pageSize;
  const currentItems = items.slice(startIndex, endIndex);
  
  return (
    <div>
      <div>
        {currentItems.map((item, index) => (
          <div key={startIndex + index}>{item}</div>
        ))}
      </div>
      <div>
        <button
          onClick={() => setCurrentPage(p => Math.max(1, p - 1))}
          disabled={currentPage === 1}
        >
          Previous
        </button>
        <span>Page {currentPage} of {totalPages}</span>
        <button
          onClick={() => setCurrentPage(p => Math.min(totalPages, p + 1))}
          disabled={currentPage === totalPages}
        >
          Next
        </button>
      </div>
    </div>
  );
}

// 无限滚动
function InfiniteScrollList({ loadMore, hasMore }) {
  const [items, setItems] = useState([]);
  const [loading, setLoading] = useState(false);
  const observerRef = useRef();
  const loaderRef = useRef();
  
  useEffect(() => {
    const options = {
      root: null,
      rootMargin: '100px',
      threshold: 0
    };
    
    observerRef.current = new IntersectionObserver((entries) => {
      if (entries[0].isIntersecting && hasMore && !loading) {
        setLoading(true);
        loadMore().then(newItems => {
          setItems(prev => [...prev, ...newItems]);
          setLoading(false);
        });
      }
    }, options);
    
    if (loaderRef.current) {
      observerRef.current.observe(loaderRef.current);
    }
    
    return () => {
      if (observerRef.current) {
        observerRef.current.disconnect();
      }
    };
  }, [loadMore, hasMore, loading]);
  
  return (
    <div>
      {items.map((item, index) => (
        <div key={index}>{item}</div>
      ))}
      {hasMore && <div ref={loaderRef}>Loading...</div>}
    </div>
  );
}

4.3 列表项优化

jsx
// ❌ 低效的列表渲染
function BadList({ items, onItemClick }) {
  return (
    <div>
      {items.map(item => (
        <div key={item.id} onClick={() => onItemClick(item)}>
          {item.name} - {item.description}
        </div>
      ))}
    </div>
  );
}

// ✅ 优化的列表渲染
function GoodList({ items, onItemClick }) {
  const handleClick = useCallback((item) => {
    onItemClick(item);
  }, [onItemClick]);
  
  return (
    <div>
      {items.map(item => (
        <ListItem key={item.id} item={item} onClick={handleClick} />
      ))}
    </div>
  );
}

const ListItem = React.memo(function({ item, onClick }) {
  const handleClick = useCallback(() => {
    onClick(item);
  }, [item, onClick]);
  
  return (
    <div onClick={handleClick}>
      {item.name} - {item.description}
    </div>
  );
});

// ✅ 更进一步: 复杂列表项
const ComplexListItem = React.memo(
  function({ item, onClick }) {
    const handleClick = useCallback(() => {
      onClick(item);
    }, [item, onClick]);
    
    // 昂贵的计算缓存
    const processedData = useMemo(() => {
      return processItemData(item);
    }, [item]);
    
    return (
      <div onClick={handleClick}>
        <h3>{item.name}</h3>
        <p>{item.description}</p>
        <ComplexVisualization data={processedData} />
      </div>
    );
  },
  (prevProps, nextProps) => {
    // 自定义比较
    return (
      prevProps.item.id === nextProps.item.id &&
      prevProps.item.updatedAt === nextProps.item.updatedAt &&
      prevProps.onClick === nextProps.onClick
    );
  }
);

5. 高级优化技巧

5.1 代码分割和懒加载

jsx
// 路由级别的代码分割
import { lazy, Suspense } from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';

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

function App() {
  return (
    <Router>
      <Suspense fallback={<div>Loading...</div>}>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/about" element={<About />} />
          <Route path="/dashboard" element={<Dashboard />} />
        </Routes>
      </Suspense>
    </Router>
  );
}

// 组件级别的代码分割
const HeavyComponent = lazy(() => import('./HeavyComponent'));

function Parent() {
  const [showHeavy, setShowHeavy] = useState(false);
  
  return (
    <div>
      <button onClick={() => setShowHeavy(true)}>Show Heavy Component</button>
      {showHeavy && (
        <Suspense fallback={<div>Loading heavy component...</div>}>
          <HeavyComponent />
        </Suspense>
      )}
    </div>
  );
}

// 预加载策略
function ComponentWithPreload() {
  const handleMouseEnter = () => {
    // 鼠标悬停时预加载
    import('./HeavyComponent');
  };
  
  return (
    <button onMouseEnter={handleMouseEnter}>
      Load Heavy Component
    </button>
  );
}

5.2 并发特性

jsx
// useTransition: 标记非紧急更新
import { useTransition, useState } from 'react';

function SearchComponent() {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState([]);
  const [isPending, startTransition] = useTransition();
  
  const handleChange = (e) => {
    const value = e.target.value;
    setQuery(value);  // 紧急更新
    
    // 非紧急更新
    startTransition(() => {
      const searchResults = performExpensiveSearch(value);
      setResults(searchResults);
    });
  };
  
  return (
    <div>
      <input value={query} onChange={handleChange} />
      {isPending && <div>Searching...</div>}
      <div>
        {results.map(result => (
          <div key={result.id}>{result.name}</div>
        ))}
      </div>
    </div>
  );
}

// useDeferredValue: 延迟值的更新
import { useDeferredValue, useMemo } from 'react';

function SearchResults({ query }) {
  const deferredQuery = useDeferredValue(query);
  
  const results = useMemo(() => {
    return performExpensiveSearch(deferredQuery);
  }, [deferredQuery]);
  
  return (
    <div>
      {results.map(result => (
        <div key={result.id}>{result.name}</div>
      ))}
    </div>
  );
}

function SearchApp() {
  const [query, setQuery] = useState('');
  
  return (
    <div>
      <input value={query} onChange={e => setQuery(e.target.value)} />
      <SearchResults query={query} />
    </div>
  );
}

5.3 批量更新

jsx
// React 18自动批量更新
function AutoBatchingExample() {
  const [count, setCount] = useState(0);
  const [flag, setFlag] = useState(false);
  
  const handleClick = () => {
    // React 18中,这些更新会自动批量处理
    setCount(c => c + 1);
    setFlag(f => !f);
    // 只会触发一次重渲染
  };
  
  const handleAsyncClick = async () => {
    await fetch('/api/data');
    
    // React 18中,即使在异步回调中也会批量更新
    setCount(c => c + 1);
    setFlag(f => !f);
    // 只会触发一次重渲染
  };
  
  return (
    <div>
      <button onClick={handleClick}>Sync Update</button>
      <button onClick={handleAsyncClick}>Async Update</button>
      <p>Count: {count}, Flag: {flag.toString()}</p>
    </div>
  );
}

// 手动退出批量更新 (如果需要)
import { flushSync } from 'react-dom';

function ManualBatchingControl() {
  const [count, setCount] = useState(0);
  const [flag, setFlag] = useState(false);
  
  const handleClick = () => {
    flushSync(() => {
      setCount(c => c + 1);  // 立即渲染
    });
    
    flushSync(() => {
      setFlag(f => !f);  // 再次渲染
    });
    // 会触发两次重渲染
  };
  
  return (
    <div>
      <button onClick={handleClick}>Update</button>
      <p>Count: {count}, Flag: {flag.toString()}</p>
    </div>
  );
}

6. 性能优化清单

typescript
const performanceOptimizationChecklist = {
  组件优化: [
    '✅ 使用React.memo避免不必要的重渲染',
    '✅ 使用useMemo缓存昂贵计算',
    '✅ 使用useCallback稳定函数引用',
    '✅ 避免内联对象和数组',
    '✅ 状态下放到需要的组件',
    '✅ 使用children prop模式'
  ],
  列表优化: [
    '✅ 使用虚拟滚动(react-window)',
    '✅ 实现分页或无限滚动',
    '✅ 优化列表项渲染',
    '✅ 使用稳定的key',
    '✅ 避免在map中使用内联函数'
  ],
  Context优化: [
    '✅ 使用useMemo缓存Context value',
    '✅ 拆分Context减小影响范围',
    '✅ 使用Context选择器',
    '✅ 考虑使用状态管理库'
  ],
  代码分割: [
    '✅ 路由级别懒加载',
    '✅ 组件级别懒加载',
    '✅ 实现预加载策略',
    '✅ 使用动态import'
  ],
  并发特性: [
    '✅ 使用useTransition标记非紧急更新',
    '✅ 使用useDeferredValue延迟值更新',
    '✅ 利用自动批量更新',
    '✅ 合理使用Suspense'
  ],
  监控和测试: [
    '✅ 使用React DevTools Profiler',
    '✅ 使用Chrome Performance工具',
    '✅ 实现自定义性能监控',
    '✅ 使用Why Did You Render',
    '✅ 编写性能测试'
  ]
};

7. 总结

渲染性能优化是React应用开发的重要环节:

  1. 识别问题: React DevTools Profiler、Chrome Performance、自定义监控
  2. 常见问题: 不必要重渲染、内联对象/函数、Context滥用、大列表
  3. 优化策略: React.memo、useMemo/useCallback、虚拟滚动、代码分割
  4. 高级技巧: 并发特性、批量更新、预加载
  5. 持续监控: 性能指标、自动化测试、用户体验

通过系统地应用这些技巧,可以构建高性能的React应用,提供流畅的用户体验。