React 性能优化是一个系统工程,涉及组件渲染、状态管理、事件处理、代码分割等多个层面。本文从实际项目经验出发,系统梳理 React 层面的性能优化手段,并提供可直接复用的代码示例和最佳实践。

一、组件渲染优化

1. React.memo - 避免不必要的重渲染

React.memo 是一个高阶组件,用于缓存组件的渲染结果,只有当 props 发生变化时才重新渲染。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import React from 'react';

// 基础用法
const ExpensiveComponent = React.memo(({ data, onUpdate }) => {
console.log('ExpensiveComponent rendered');
return (
<div>
{data.map(item => (
<div key={item.id}>{item.name}</div>
))}
<button onClick={onUpdate}>更新</button>
</div>
);
});

// 自定义比较函数
const CustomMemoComponent = React.memo(
({ data, onUpdate }) => {
return <div>{/* 组件内容 */}</div>;
},
(prevProps, nextProps) => {
// 返回 true 表示不重新渲染,false 表示重新渲染
return prevProps.data.length === nextProps.data.length;
}
);

2. useMemo - 缓存计算结果

useMemo 用于缓存昂贵的计算结果,避免在每次渲染时重复计算。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import React, { useMemo } from 'react';

function DataTable({ data, filterText }) {
// 缓存过滤后的数据
const filteredData = useMemo(() => {
console.log('重新计算过滤数据');
return data.filter(item =>
item.name.toLowerCase().includes(filterText.toLowerCase())
);
}, [data, filterText]); // 依赖项

// 缓存排序后的数据
const sortedData = useMemo(() => {
return [...filteredData].sort((a, b) => a.name.localeCompare(b.name));
}, [filteredData]);

return (
<table>
{sortedData.map(item => (
<tr key={item.id}>
<td>{item.name}</td>
<td>{item.value}</td>
</tr>
))}
</table>
);
}

3. useCallback - 缓存函数引用

useCallback 用于缓存函数引用,避免子组件因为父组件重新渲染而重新渲染。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import React, { useCallback, useState } from 'react';

function ParentComponent() {
const [count, setCount] = useState(0);
const [data, setData] = useState([]);

// 缓存事件处理函数
const handleAddItem = useCallback((newItem) => {
setData(prev => [...prev, newItem]);
}, []); // 空依赖数组,函数引用永远不变

// 缓存带参数的函数
const handleUpdateItem = useCallback((id, updates) => {
setData(prev => prev.map(item =>
item.id === id ? { ...item, ...updates } : item
));
}, []);

return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(c => c + 1)}>增加计数</button>
<ChildComponent data={data} onAdd={handleAddItem} onUpdate={handleUpdateItem} />
</div>
);
}

const ChildComponent = React.memo(({ data, onAdd, onUpdate }) => {
return (
<div>
{data.map(item => (
<div key={item.id}>
{item.name}
<button onClick={() => onUpdate(item.id, { name: 'Updated' })}>
更新
</button>
</div>
))}
</div>
);
});

二、状态管理优化

1. 状态分割 - 避免不必要的重渲染

将大的状态对象分割成多个小的状态,避免一个状态变化导致整个组件重新渲染。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import React, { useState } from 'react';

// ❌ 不好的做法:一个大状态对象
function BadExample() {
const [state, setState] = useState({
user: { name: 'John', email: '[email protected]' },
settings: { theme: 'dark', language: 'en' },
notifications: { count: 5, list: [] }
});

const updateUser = (userData) => {
setState(prev => ({ ...prev, user: { ...prev.user, ...userData } }));
};

// 更新用户信息会导致整个组件重新渲染
return <div>{/* 组件内容 */}</div>;
}

// ✅ 好的做法:状态分割
function GoodExample() {
const [user, setUser] = useState({ name: 'John', email: '[email protected]' });
const [settings, setSettings] = useState({ theme: 'dark', language: 'en' });
const [notifications, setNotifications] = useState({ count: 5, list: [] });

const updateUser = (userData) => {
setUser(prev => ({ ...prev, ...userData }));
};

// 只有用户信息变化时才会重新渲染
return <div>{/* 组件内容 */}</div>;
}

2. 使用 useReducer 管理复杂状态

对于复杂的状态逻辑,使用 useReducer 可以更好地管理状态更新。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
import React, { useReducer } from 'react';

const initialState = {
items: [],
loading: false,
error: null,
filter: 'all'
};

function reducer(state, action) {
switch (action.type) {
case 'SET_LOADING':
return { ...state, loading: action.payload };
case 'SET_ITEMS':
return { ...state, items: action.payload, loading: false };
case 'ADD_ITEM':
return { ...state, items: [...state.items, action.payload] };
case 'REMOVE_ITEM':
return {
...state,
items: state.items.filter(item => item.id !== action.payload)
};
case 'SET_FILTER':
return { ...state, filter: action.payload };
case 'SET_ERROR':
return { ...state, error: action.payload, loading: false };
default:
return state;
}
}

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

const addItem = (item) => {
dispatch({ type: 'ADD_ITEM', payload: item });
};

const removeItem = (id) => {
dispatch({ type: 'REMOVE_ITEM', payload: id });
};

return (
<div>
{state.loading && <div>加载中...</div>}
{state.error && <div>错误: {state.error}</div>}
{state.items.map(item => (
<div key={item.id}>
{item.name}
<button onClick={() => removeItem(item.id)}>删除</button>
</div>
))}
</div>
);
}

三、列表渲染优化

1. 虚拟列表 - 处理大量数据

对于大量数据的列表渲染,使用虚拟列表可以显著提升性能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import React, { useState, useMemo } from 'react';

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

// 计算可见区域
const visibleRange = useMemo(() => {
const startIndex = Math.floor(scrollTop / itemHeight);
const endIndex = Math.min(
startIndex + Math.ceil(containerHeight / itemHeight) + 1,
items.length
);
return { startIndex, endIndex };
}, [scrollTop, itemHeight, containerHeight, items.length]);

// 只渲染可见的项目
const visibleItems = useMemo(() => {
return items.slice(visibleRange.startIndex, visibleRange.endIndex);
}, [items, visibleRange]);

const handleScroll = (e) => {
setScrollTop(e.target.scrollTop);
};

return (
<div
style={{ height: containerHeight, overflow: 'auto' }}
onScroll={handleScroll}
>
<div style={{ height: items.length * itemHeight }}>
<div style={{ transform: `translateY(${visibleRange.startIndex * itemHeight}px)` }}>
{visibleItems.map((item, index) => (
<div
key={item.id}
style={{ height: itemHeight, borderBottom: '1px solid #eee' }}
>
{item.name}
</div>
))}
</div>
</div>
</div>
);
}

2. 使用稳定的 key

确保列表项的 key 是稳定且唯一的,避免 React 的 diff 算法出现问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// ❌ 不好的做法:使用索引作为 key
function BadList({ items }) {
return (
<ul>
{items.map((item, index) => (
<li key={index}>{item.name}</li> // 索引会变化
))}
</ul>
);
}

// ✅ 好的做法:使用唯一 ID
function GoodList({ items }) {
return (
<ul>
{items.map(item => (
<li key={item.id}>{item.name}</li> // 使用稳定的 ID
))}
</ul>
);
}

四、事件处理优化

1. 事件委托

对于大量相似元素的事件处理,使用事件委托可以减少事件监听器的数量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import React, { useCallback } from 'react';

function EventDelegationExample({ items }) {
const handleClick = useCallback((e) => {
const target = e.target;
if (target.matches('.item-button')) {
const itemId = target.dataset.id;
console.log('点击了项目:', itemId);
}
}, []);

return (
<div onClick={handleClick}>
{items.map(item => (
<div key={item.id} className="item">
{item.name}
<button className="item-button" data-id={item.id}>
操作
</button>
</div>
))}
</div>
);
}

2. 防抖和节流

对于频繁触发的事件,使用防抖和节流可以优化性能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
import React, { useState, useCallback } from 'react';

// 防抖 Hook
function useDebounce(callback, delay) {
const [timeoutId, setTimeoutId] = useState(null);

return useCallback((...args) => {
if (timeoutId) {
clearTimeout(timeoutId);
}
const newTimeoutId = setTimeout(() => callback(...args), delay);
setTimeoutId(newTimeoutId);
}, [callback, delay, timeoutId]);
}

// 节流 Hook
function useThrottle(callback, delay) {
const [lastCall, setLastCall] = useState(0);

return useCallback((...args) => {
const now = Date.now();
if (now - lastCall >= delay) {
callback(...args);
setLastCall(now);
}
}, [callback, delay, lastCall]);
}

function SearchComponent() {
const [searchTerm, setSearchTerm] = useState('');

const debouncedSearch = useDebounce((term) => {
console.log('搜索:', term);
// 执行搜索逻辑
}, 300);

const throttledScroll = useThrottle(() => {
console.log('滚动事件');
// 处理滚动逻辑
}, 100);

const handleInputChange = (e) => {
const value = e.target.value;
setSearchTerm(value);
debouncedSearch(value);
};

return (
<div onScroll={throttledScroll}>
<input
value={searchTerm}
onChange={handleInputChange}
placeholder="搜索..."
/>
</div>
);
}

五、代码分割与懒加载

1. React.lazy 和 Suspense

使用 React.lazySuspense 实现组件的懒加载。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import React, { Suspense, lazy } from 'react';

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

function App() {
const [showLazy, setShowLazy] = useState(false);

return (
<div>
<button onClick={() => setShowLazy(!showLazy)}>
切换懒加载组件
</button>

{showLazy && (
<Suspense fallback={<div>加载中...</div>}>
<LazyComponent />
</Suspense>
)}
</div>
);
}

2. 路由级别的代码分割

在路由层面实现代码分割,减少初始包的大小。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import React, { Suspense, lazy } from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';

// 懒加载页面组件
const Home = lazy(() => import('./pages/Home'));
const About = lazy(() => import('./pages/About'));
const Contact = lazy(() => import('./pages/Contact'));

function App() {
return (
<BrowserRouter>
<Suspense fallback={<div>页面加载中...</div>}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/contact" element={<Contact />} />
</Routes>
</Suspense>
</BrowserRouter>
);
}

六、Context 优化

1. 分割 Context

将大的 Context 分割成多个小的 Context,避免不必要的重渲染。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
import React, { createContext, useContext, useState } from 'react';

// 分割 Context
const UserContext = createContext();
const ThemeContext = createContext();
const NotificationContext = createContext();

function UserProvider({ children }) {
const [user, setUser] = useState(null);
return (
<UserContext.Provider value={{ user, setUser }}>
{children}
</UserContext.Provider>
);
}

function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
{children}
</ThemeContext.Provider>
);
}

function NotificationProvider({ children }) {
const [notifications, setNotifications] = useState([]);
return (
<NotificationContext.Provider value={{ notifications, setNotifications }}>
{children}
</NotificationContext.Provider>
);
}

// 使用 Hook
function useUser() {
const context = useContext(UserContext);
if (!context) {
throw new Error('useUser must be used within UserProvider');
}
return context;
}

function useTheme() {
const context = useContext(ThemeContext);
if (!context) {
throw new Error('useTheme must be used within ThemeProvider');
}
return context;
}

function App() {
return (
<UserProvider>
<ThemeProvider>
<NotificationProvider>
<MainApp />
</NotificationProvider>
</ThemeProvider>
</UserProvider>
);
}

2. 使用 useMemo 优化 Context 值

避免 Context 值在每次渲染时都创建新对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import React, { createContext, useContext, useState, useMemo } from 'react';

const AppContext = createContext();

function AppProvider({ children }) {
const [user, setUser] = useState(null);
const [theme, setTheme] = useState('light');

// 使用 useMemo 缓存 Context 值
const contextValue = useMemo(() => ({
user,
setUser,
theme,
setTheme,
isLoggedIn: !!user,
isDarkTheme: theme === 'dark'
}), [user, theme]);

return (
<AppContext.Provider value={contextValue}>
{children}
</AppContext.Provider>
);
}

七、Ref 优化

1. 使用 useRef 避免不必要的重渲染

对于不需要触发重渲染的值,使用 useRef 而不是 useState

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import React, { useRef, useEffect } from 'react';

function TimerComponent() {
const intervalRef = useRef(null);
const countRef = useRef(0);

useEffect(() => {
intervalRef.current = setInterval(() => {
countRef.current += 1;
console.log('计数:', countRef.current);
}, 1000);

return () => {
if (intervalRef.current) {
clearInterval(intervalRef.current);
}
};
}, []);

return <div>计时器运行中...</div>;
}

2. 使用 useImperativeHandle 暴露方法

通过 useImperativeHandle 向父组件暴露子组件的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import React, { forwardRef, useImperativeHandle, useState } from 'react';

const ChildComponent = forwardRef((props, ref) => {
const [count, setCount] = useState(0);

useImperativeHandle(ref, () => ({
increment: () => setCount(c => c + 1),
decrement: () => setCount(c => c - 1),
reset: () => setCount(0),
getCount: () => count
}));

return <div>计数: {count}</div>;
});

function ParentComponent() {
const childRef = useRef();

const handleIncrement = () => {
childRef.current?.increment();
};

const handleDecrement = () => {
childRef.current?.decrement();
};

return (
<div>
<ChildComponent ref={childRef} />
<button onClick={handleIncrement}>增加</button>
<button onClick={handleDecrement}>减少</button>
</div>
);
}

八、开发工具与调试

1. React DevTools Profiler

使用 React DevTools 的 Profiler 工具分析组件渲染性能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import React, { Profiler } from 'react';

function onRenderCallback(
id, // Profiler 树的 id
phase, // "mount" (首次挂载) 或 "update" (重新渲染)
actualDuration, // 渲染花费的时间
baseDuration, // 估计不使用 memoization 的情况下渲染整棵子树需要的时间
startTime, // 渲染开始的时间
commitTime, // 渲染提交的时间
interactions // 属于这次更新的 interactions 的集合
) {
console.log('Profiler:', {
id,
phase,
actualDuration,
baseDuration,
startTime,
commitTime
});
}

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

2. 使用 why-did-you-render

使用 why-did-you-render 库来调试不必要的重渲染。

1
2
3
4
5
6
7
8
9
10
// 在开发环境中启用
if (process.env.NODE_ENV === 'development') {
const whyDidYouRender = require('@welldone-software/why-did-you-render');
whyDidYouRender(React, {
trackAllPureComponents: true,
});
}

// 在组件上启用
MyComponent.whyDidYouRender = true;

九、性能优化对比总结

优化手段 适用场景 性能提升 实现复杂度 注意事项
React.memo 纯组件,props 变化不频繁 中等 避免过度使用,合理设置比较函数
useMemo 昂贵计算,依赖项变化不频繁 正确设置依赖项,避免过度缓存
useCallback 函数作为 props 传递给子组件 中等 合理设置依赖项,避免空依赖
状态分割 复杂状态管理 避免状态过度分割
useReducer 复杂状态逻辑 中等 适合复杂状态更新逻辑
虚拟列表 大量数据渲染 很高 需要精确计算可见区域
事件委托 大量相似元素事件 中等 需要正确的事件目标判断
防抖/节流 频繁触发的事件 合理设置延迟时间
代码分割 大型应用 很高 需要合理的分割策略
Context 分割 复杂状态共享 避免 Context 嵌套过深
useRef 不需要重渲染的值 中等 避免过度使用 ref

十、最佳实践总结

  1. 测量优先:在优化之前,先使用 React DevTools Profiler 测量性能瓶颈。
  2. 渐进优化:从影响最大的优化开始,逐步应用其他优化手段。
  3. 避免过度优化:不是所有组件都需要优化,优先优化性能瓶颈。
  4. 保持可读性:优化不应该牺牲代码的可读性和可维护性。
  5. 测试验证:每次优化后都要测试验证效果,确保没有引入新的问题。

结语

React 性能优化是一个持续的过程,需要根据具体的应用场景和性能瓶颈来选择合适的优化手段。通过合理使用这些优化技巧,可以显著提升 React 应用的性能表现,为用户提供更好的使用体验。

Happy Coding!