性能优化 #

一、性能优化概述 #

1.1 优化目标 #

目标 说明
首屏加载 减少初始加载时间
运行性能 减少不必要的渲染
内存占用 避免内存泄漏
用户体验 保持流畅交互

1.2 优化策略 #

text
性能优化
├── 组件优化
│   ├── memo
│   └── 组件拆分
├── 渲染优化
│   ├── useMemo
│   ├── useCallback
│   └── 虚拟化
├── 代码优化
│   ├── 代码分割
│   └── 懒加载
└── 资源优化
    ├── 图片优化
    └── 缓存策略

二、组件优化 #

2.1 memo 避免重渲染 #

jsx
import { memo } from 'preact/compat';

// 普通组件:父组件更新时子组件也会更新
function Child({ name }) {
  console.log('Child rendered');
  return <div>{name}</div>;
}

// 使用 memo:props 不变时不重新渲染
const MemoChild = memo(function Child({ name }) {
  console.log('MemoChild rendered');
  return <div>{name}</div>;
});

// 使用
function Parent() {
  const [count, setCount] = useState(0);
  const [name, setName] = useState('Alice');

  return (
    <div>
      <button onClick={() => setCount(c => c + 1)}>{count}</button>
      <MemoChild name={name} />
    </div>
  );
}

2.2 自定义比较函数 #

jsx
const MemoComponent = memo(
  function Component({ user }) {
    return <div>{user.name}</div>;
  },
  (prevProps, nextProps) => {
    // 返回 true 表示不需要重新渲染
    return prevProps.user.id === nextProps.user.id;
  }
);

2.3 组件拆分 #

jsx
// ❌ 整个组件都会重新渲染
function BadExample({ items, selectedItem }) {
  return (
    <div>
      <h1>Items</h1>
      <ul>
        {items.map(item => (
          <li key={item.id} class={item.id === selectedItem.id ? 'selected' : ''}>
            {item.name}
          </li>
        ))}
      </ul>
      <SelectedItem item={selectedItem} />
    </div>
  );
}

// ✅ 拆分组件,减少渲染范围
function GoodExample({ items, selectedItem }) {
  return (
    <div>
      <h1>Items</h1>
      <ItemList items={items} selectedId={selectedItem?.id} />
      <SelectedItem item={selectedItem} />
    </div>
  );
}

const ItemList = memo(function ItemList({ items, selectedId }) {
  return (
    <ul>
      {items.map(item => (
        <li key={item.id} class={item.id === selectedId ? 'selected' : ''}>
          {item.name}
        </li>
      ))}
    </ul>
  );
});

三、渲染优化 #

3.1 useMemo 缓存计算 #

jsx
import { useMemo } from 'preact/hooks';

function ExpensiveList({ items, filter, sortBy }) {
  // 缓存过滤结果
  const filteredItems = useMemo(() => {
    console.log('Filtering...');
    return items.filter(item => item.name.includes(filter));
  }, [items, filter]);

  // 缓存排序结果
  const sortedItems = useMemo(() => {
    console.log('Sorting...');
    return [...filteredItems].sort((a, b) => {
      if (sortBy === 'name') return a.name.localeCompare(b.name);
      if (sortBy === 'date') return new Date(b.date) - new Date(a.date);
      return 0;
    });
  }, [filteredItems, sortBy]);

  return (
    <ul>
      {sortedItems.map(item => (
        <li key={item.id}>{item.name}</li>
      ))}
    </ul>
  );
}

3.2 useCallback 缓存函数 #

jsx
import { useCallback } from 'preact/hooks';

function Parent() {
  const [count, setCount] = useState(0);
  const [items, setItems] = useState([]);

  // 缓存回调函数
  const handleAddItem = useCallback(() => {
    setItems(prev => [...prev, `Item ${prev.length + 1}`]);
  }, []);

  const handleRemoveItem = useCallback((id) => {
    setItems(prev => prev.filter(item => item.id !== id));
  }, []);

  return (
    <div>
      <button onClick={() => setCount(c => c + 1)}>{count}</button>
      <ItemList 
        items={items}
        onAdd={handleAddItem}
        onRemove={handleRemoveItem}
      />
    </div>
  );
}

const ItemList = memo(function ItemList({ items, onAdd, onRemove }) {
  console.log('ItemList rendered');
  return (
    <div>
      <button onClick={onAdd}>Add</button>
      <ul>
        {items.map(item => (
          <li key={item.id}>
            {item.name}
            <button onClick={() => onRemove(item.id)}>Remove</button>
          </li>
        ))}
      </ul>
    </div>
  );
});

3.3 避免内联对象和函数 #

jsx
// ❌ 每次渲染都创建新对象/函数
function BadExample({ data }) {
  return (
    <ChildComponent
      style={{ color: 'red' }}
      onClick={() => console.log('clicked')}
      data={data}
    />
  );
}

// ✅ 提取为常量或使用 useMemo/useCallback
const style = { color: 'red' };

function GoodExample({ data }) {
  const handleClick = useCallback(() => {
    console.log('clicked');
  }, []);

  return (
    <ChildComponent
      style={style}
      onClick={handleClick}
      data={data}
    />
  );
}

四、列表虚拟化 #

4.1 为什么需要虚拟化 #

text
普通列表:渲染所有元素
┌─────────────────────┐
│ Item 1              │
│ Item 2              │
│ Item 3              │
│ ...                 │
│ Item 10000          │ ← 10000 个 DOM 节点
└─────────────────────┘

虚拟列表:只渲染可见元素
┌─────────────────────┐
│ Item 1              │
│ Item 2              │
│ Item 3              │ ← 只有可见的几个节点
│ Item 4              │
│ Item 5              │
└─────────────────────┘

4.2 简单虚拟列表实现 #

jsx
function VirtualList({ items, itemHeight = 50, containerHeight = 400 }) {
  const [scrollTop, setScrollTop] = useState(0);

  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 handleScroll = (e) => {
    setScrollTop(e.target.scrollTop);
  };

  return (
    <div
      style={{ height: containerHeight, overflow: 'auto' }}
      onScroll={handleScroll}
    >
      <div style={{ height: items.length * itemHeight, position: 'relative' }}>
        {visibleItems.map((item, index) => (
          <div
            key={startIndex + index}
            style={{
              position: 'absolute',
              top: (startIndex + index) * itemHeight,
              height: itemHeight
            }}
          >
            {item.content}
          </div>
        ))}
      </div>
    </div>
  );
}

4.3 使用第三方库 #

jsx
import { useVirtual } from '@tanstack/react-virtual';

function VirtualizedList({ items }) {
  const parentRef = useRef(null);

  const rowVirtualizer = useVirtual({
    size: items.length,
    parentRef,
    estimateSize: () => 50,
    overscan: 5
  });

  return (
    <div
      ref={parentRef}
      style={{ height: '400px', overflow: 'auto' }}
    >
      <div
        style={{
          height: `${rowVirtualizer.totalSize}px`,
          position: 'relative'
        }}
      >
        {rowVirtualizer.virtualItems.map(virtualRow => (
          <div
            key={virtualRow.index}
            style={{
              position: 'absolute',
              top: 0,
              left: 0,
              width: '100%',
              height: `${virtualRow.size}px`,
              transform: `translateY(${virtualRow.start}px)`
            }}
          >
            {items[virtualRow.index].content}
          </div>
        ))}
      </div>
    </div>
  );
}

五、代码分割 #

5.1 动态导入 #

jsx
import { lazy, Suspense } from 'preact/compat';

// 懒加载组件
const HeavyComponent = lazy(() => import('./HeavyComponent'));

function App() {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <HeavyComponent />
    </Suspense>
  );
}

5.2 路由级别分割 #

jsx
import { lazy, Suspense } from 'preact/compat';

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

function App() {
  const [route, setRoute] = useState('home');

  const renderPage = () => {
    switch (route) {
      case 'home':
        return <Home />;
      case 'about':
        return <About />;
      case 'dashboard':
        return <Dashboard />;
      default:
        return <Home />;
    }
  };

  return (
    <div>
      <nav>
        <button onClick={() => setRoute('home')}>Home</button>
        <button onClick={() => setRoute('about')}>About</button>
        <button onClick={() => setRoute('dashboard')}>Dashboard</button>
      </nav>
      <Suspense fallback={<div>Loading...</div>}>
        {renderPage()}
      </Suspense>
    </div>
  );
}

5.3 条件加载 #

jsx
function App() {
  const [showChart, setShowChart] = useState(false);
  const [Chart, setChart] = useState(null);

  const loadChart = async () => {
    const module = await import('./Chart');
    setChart(() => module.default);
    setShowChart(true);
  };

  return (
    <div>
      <button onClick={loadChart}>Show Chart</button>
      {showChart && Chart && <Chart data={chartData} />}
    </div>
  );
}

六、状态优化 #

6.1 状态下沉 #

jsx
// ❌ 状态在顶层,所有组件都会重新渲染
function BadApp() {
  const [inputValue, setInputValue] = useState('');

  return (
    <div>
      <Header />
      <Main />
      <Footer />
      <input 
        value={inputValue} 
        onInput={(e) => setInputValue(e.target.value)} 
      />
    </div>
  );
}

// ✅ 状态就近管理
function GoodApp() {
  return (
    <div>
      <Header />
      <Main />
      <Footer />
      <SearchInput />
    </div>
  );
}

function SearchInput() {
  const [inputValue, setInputValue] = useState('');
  
  return (
    <input 
      value={inputValue} 
      onInput={(e) => setInputValue(e.target.value)} 
    />
  );
}

6.2 状态合并 #

jsx
// ❌ 多个独立状态
function BadForm() {
  const [firstName, setFirstName] = useState('');
  const [lastName, setLastName] = useState('');
  const [email, setEmail] = useState('');
  // 每个状态更新都会触发渲染
}

// ✅ 合并状态
function GoodForm() {
  const [form, setForm] = useState({
    firstName: '',
    lastName: '',
    email: ''
  });

  const updateField = (field, value) => {
    setForm(prev => ({ ...prev, [field]: value }));
  };
}

6.3 使用 useReducer #

jsx
import { useReducer } from 'preact/hooks';

const initialState = {
  items: [],
  filter: 'all',
  sortBy: 'date'
};

function reducer(state, action) {
  switch (action.type) {
    case 'add':
      return { ...state, items: [...state.items, action.payload] };
    case 'remove':
      return { ...state, items: state.items.filter(i => i.id !== action.payload) };
    case 'set_filter':
      return { ...state, filter: action.payload };
    case 'set_sort':
      return { ...state, sortBy: action.payload };
    default:
      return state;
  }
}

function ItemManager() {
  const [state, dispatch] = useReducer(reducer, initialState);

  // 使用 dispatch 不会在每次渲染时创建新函数
  return (
    <div>
      <button onClick={() => dispatch({ type: 'add', payload: newItem })}>
        Add
      </button>
      <button onClick={() => dispatch({ type: 'set_filter', payload: 'active' })}>
        Filter Active
      </button>
    </div>
  );
}

七、性能监控 #

7.1 使用 Profiler #

jsx
import { Profiler } from 'preact';

function onRenderCallback(
  id,
  phase,
  actualDuration,
  baseDuration,
  startTime,
  commitTime
) {
  console.log(`${id} ${phase} took ${actualDuration}ms`);
}

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

7.2 性能测量 #

jsx
function useRenderCount(name) {
  const count = useRef(0);
  
  useEffect(() => {
    count.current += 1;
    console.log(`${name} rendered ${count.current} times`);
  });
}

function MyComponent() {
  useRenderCount('MyComponent');
  return <div>Content</div>;
}

八、最佳实践总结 #

8.1 优化清单 #

优化项 方法
避免重渲染 memo, useMemo, useCallback
列表优化 虚拟化, key 优化
代码分割 lazy, Suspense
状态管理 下沉, 合并, useReducer
资源优化 图片懒加载, 缓存

8.2 不要过度优化 #

jsx
// ❌ 不必要的优化
function SimpleComponent({ name }) {
  const greeting = useMemo(() => `Hello, ${name}`, [name]);
  return <div>{greeting}</div>;
}

// ✅ 简单计算不需要优化
function SimpleComponent({ name }) {
  return <div>Hello, {name}</div>;
}

九、总结 #

要点 说明
memo 避免不必要的重渲染
useMemo 缓存计算结果
useCallback 缓存函数引用
虚拟化 长列表优化
代码分割 减少初始加载

核心原则:

  • 先测量,后优化
  • 避免过度优化
  • 关注用户感知性能
  • 保持代码可读性
最后更新:2026-03-28