性能优化 #
一、性能优化概述 #
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