Appearance
Context与Redux对比
1. Context基础
1.1 Context核心概念
jsx
// 创建Context
const ThemeContext = React.createContext('light');
// Provider
function App() {
const [theme, setTheme] = useState('light');
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
<Toolbar />
</ThemeContext.Provider>
);
}
// Consumer
function ThemedButton() {
const { theme, setTheme } = useContext(ThemeContext);
return (
<button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
{theme}
</button>
);
}1.2 Context的优势
typescript
const contextAdvantages = {
'简单易用': '无需额外依赖,React内置',
'轻量': '代码量少,学习成本低',
'适合小型应用': '简单的全局状态管理',
'类型安全': '配合TypeScript易于类型推导'
};1.3 Context的劣势
typescript
const contextDisadvantages = {
'性能问题': 'Context变化时,所有消费者都会重渲染',
'缺乏工具': '没有DevTools、时间旅行等调试工具',
'异步处理': '没有内置的异步处理机制',
'持久化': '没有内置的状态持久化方案',
'中间件': '无法使用中间件扩展功能'
};2. Redux基础
2.1 Redux核心概念
javascript
// Store
const store = createStore(rootReducer);
// Action
const increment = () => ({ type: 'INCREMENT' });
// Reducer
function counterReducer(state = 0, action) {
switch (action.type) {
case 'INCREMENT':
return state + 1;
default:
return state;
}
}
// 使用
function Counter() {
const count = useSelector(state => state.counter);
const dispatch = useDispatch();
return (
<button onClick={() => dispatch(increment())}>
{count}
</button>
);
}2.2 Redux的优势
typescript
const reduxAdvantages = {
'强大工具': 'Redux DevTools、时间旅行调试',
'中间件生态': '丰富的中间件(thunk、saga等)',
'性能优化': '精确的订阅机制,避免不必要渲染',
'可预测': '单向数据流,状态变化可追踪',
'成熟生态': '大量的最佳实践和库支持',
'持久化': '易于实现状态持久化'
};2.3 Redux的劣势
typescript
const reduxDisadvantages = {
'样板代码多': '需要actions、reducers、types',
'学习曲线': '概念多,需要理解中间件、reducer等',
'额外依赖': '需要安装redux和react-redux',
'过度设计': '小型应用可能过于复杂'
};3. 性能对比
3.1 Context性能问题
jsx
// 问题:Context变化导致所有消费者重渲染
const AppContext = createContext();
function App() {
const [user, setUser] = useState(null);
const [theme, setTheme] = useState('light');
// 问题:user或theme任一变化,所有消费者都重渲染
return (
<AppContext.Provider value={{ user, setUser, theme, setTheme }}>
<Header />
<Content />
<Footer />
</AppContext.Provider>
);
}
function Header() {
// 即使只使用theme,user变化也会导致重渲染
const { theme } = useContext(AppContext);
return <header className={theme}>Header</header>;
}3.2 Context性能优化
jsx
// 解决方案1:拆分Context
const UserContext = createContext();
const ThemeContext = createContext();
function App() {
const [user, setUser] = useState(null);
const [theme, setTheme] = useState('light');
return (
<UserContext.Provider value={{ user, setUser }}>
<ThemeContext.Provider value={{ theme, setTheme }}>
<Header />
<Content />
<Footer />
</ThemeContext.Provider>
</UserContext.Provider>
);
}
// 解决方案2:useMemo
function App() {
const [user, setUser] = useState(null);
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}>
<Header />
<Content />
<Footer />
</ThemeContext.Provider>
</UserContext.Provider>
);
}
// 解决方案3:Context Selector模式
function createSelectorContext(initialState) {
const context = createContext(null);
function Provider({ children, value }) {
const stateRef = useRef(value);
const listenersRef = useRef(new Set());
useEffect(() => {
stateRef.current = value;
listenersRef.current.forEach(listener => listener());
}, [value]);
const subscribe = useCallback((listener) => {
listenersRef.current.add(listener);
return () => listenersRef.current.delete(listener);
}, []);
const getState = useCallback(() => stateRef.current, []);
return (
<context.Provider value={{ subscribe, getState }}>
{children}
</context.Provider>
);
}
function useSelector(selector) {
const { subscribe, getState } = useContext(context);
const [, forceUpdate] = useState({});
const selectorRef = useRef(selector);
const selectedRef = useRef();
selectorRef.current = selector;
selectedRef.current = selector(getState());
useEffect(() => {
const checkUpdate = () => {
const newSelected = selectorRef.current(getState());
if (newSelected !== selectedRef.current) {
selectedRef.current = newSelected;
forceUpdate({});
}
};
return subscribe(checkUpdate);
}, [subscribe, getState]);
return selectedRef.current;
}
return { Provider, useSelector };
}
// 使用
const { Provider, useSelector } = createSelectorContext({
user: null,
theme: 'light'
});
function Header() {
// 只订阅theme,user变化不会重渲染
const theme = useSelector(state => state.theme);
return <header className={theme}>Header</header>;
}3.3 Redux性能优势
jsx
// Redux的精确订阅
function Header() {
// 只订阅theme,其他state变化不会重渲染
const theme = useSelector(state => state.theme);
return <header className={theme}>Header</header>;
}
function UserProfile() {
// 只订阅user,theme变化不会重渲染
const user = useSelector(state => state.user);
return <div>{user?.name}</div>;
}
// Redux Toolkit的createSelector
import { createSelector } from '@reduxjs/toolkit';
const selectUser = state => state.user;
const selectPosts = state => state.posts;
const selectUserPosts = createSelector(
[selectUser, selectPosts],
(user, posts) => posts.filter(post => post.authorId === user.id)
);
function UserPosts() {
// memoized selector,只在user或posts变化时重新计算
const userPosts = useSelector(selectUserPosts);
return <PostList posts={userPosts} />;
}4. 使用场景对比
4.1 Context适用场景
typescript
const contextUseCases = {
'主题切换': {
reason: '简单的全局配置,变化不频繁',
example: '亮色/暗色主题'
},
'语言切换': {
reason: '全局配置,变化较少',
example: '中文/英文切换'
},
'用户认证': {
reason: '登录状态,变化不频繁',
example: '登录/登出'
},
'配置信息': {
reason: '应用级配置,基本不变',
example: 'API endpoint、feature flags'
},
'小型应用': {
reason: '状态简单,不需要复杂工具',
example: 'Todo应用、简单表单'
}
};4.2 Redux适用场景
typescript
const reduxUseCases = {
'大型应用': {
reason: '状态复杂,需要强大的状态管理',
example: '电商平台、社交媒体'
},
'频繁更新': {
reason: '高频状态变化,需要性能优化',
example: '实时数据看板、游戏'
},
'需要调试': {
reason: 'Redux DevTools、时间旅行',
example: '复杂业务逻辑调试'
},
'异步操作': {
reason: '丰富的异步中间件',
example: 'API调用、WebSocket'
},
'状态持久化': {
reason: '易于实现本地存储',
example: '用户偏好、购物车'
},
'跨组件通信': {
reason: '复杂的组件间数据共享',
example: '多步骤表单、协作编辑'
}
};5. 代码量对比
5.1 Context实现Todo应用
jsx
// Context版本(约50行)
const TodoContext = createContext();
function TodoProvider({ children }) {
const [todos, setTodos] = useState([]);
const addTodo = (text) => {
setTodos([...todos, {
id: Date.now(),
text,
completed: false
}]);
};
const toggleTodo = (id) => {
setTodos(todos.map(todo =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
));
};
const deleteTodo = (id) => {
setTodos(todos.filter(todo => todo.id !== id));
};
return (
<TodoContext.Provider value={{ todos, addTodo, toggleTodo, deleteTodo }}>
{children}
</TodoContext.Provider>
);
}
function TodoList() {
const { todos, toggleTodo, deleteTodo } = useContext(TodoContext);
return (
<ul>
{todos.map(todo => (
<li key={todo.id}>
<input
type="checkbox"
checked={todo.completed}
onChange={() => toggleTodo(todo.id)}
/>
<span>{todo.text}</span>
<button onClick={() => deleteTodo(todo.id)}>Delete</button>
</li>
))}
</ul>
);
}
function AddTodo() {
const { addTodo } = useContext(TodoContext);
const [text, setText] = useState('');
const handleSubmit = (e) => {
e.preventDefault();
if (text.trim()) {
addTodo(text);
setText('');
}
};
return (
<form onSubmit={handleSubmit}>
<input value={text} onChange={(e) => setText(e.target.value)} />
<button type="submit">Add</button>
</form>
);
}5.2 Redux实现Todo应用
jsx
// Redux版本(约100行)
// types.js
const ADD_TODO = 'ADD_TODO';
const TOGGLE_TODO = 'TOGGLE_TODO';
const DELETE_TODO = 'DELETE_TODO';
// actions.js
const addTodo = (text) => ({
type: ADD_TODO,
payload: {
id: Date.now(),
text,
completed: false
}
});
const toggleTodo = (id) => ({
type: TOGGLE_TODO,
payload: id
});
const deleteTodo = (id) => ({
type: DELETE_TODO,
payload: id
});
// reducer.js
function todosReducer(state = [], action) {
switch (action.type) {
case ADD_TODO:
return [...state, action.payload];
case TOGGLE_TODO:
return state.map(todo =>
todo.id === action.payload
? { ...todo, completed: !todo.completed }
: todo
);
case DELETE_TODO:
return state.filter(todo => todo.id !== action.payload);
default:
return state;
}
}
// store.js
const store = createStore(todosReducer);
// TodoList.jsx
function TodoList() {
const todos = useSelector(state => state);
const dispatch = useDispatch();
return (
<ul>
{todos.map(todo => (
<li key={todo.id}>
<input
type="checkbox"
checked={todo.completed}
onChange={() => dispatch(toggleTodo(todo.id))}
/>
<span>{todo.text}</span>
<button onClick={() => dispatch(deleteTodo(todo.id))}>Delete</button>
</li>
))}
</ul>
);
}
// AddTodo.jsx
function AddTodo() {
const dispatch = useDispatch();
const [text, setText] = useState('');
const handleSubmit = (e) => {
e.preventDefault();
if (text.trim()) {
dispatch(addTodo(text));
setText('');
}
};
return (
<form onSubmit={handleSubmit}>
<input value={text} onChange={(e) => setText(e.target.value)} />
<button type="submit">Add</button>
</form>
);
}5.3 Redux Toolkit实现
jsx
// Redux Toolkit版本(约60行)
// todosSlice.js
import { createSlice } from '@reduxjs/toolkit';
const todosSlice = createSlice({
name: 'todos',
initialState: [],
reducers: {
addTodo: (state, action) => {
state.push({
id: Date.now(),
text: action.payload,
completed: false
});
},
toggleTodo: (state, action) => {
const todo = state.find(t => t.id === action.payload);
if (todo) {
todo.completed = !todo.completed;
}
},
deleteTodo: (state, action) => {
return state.filter(t => t.id !== action.payload);
}
}
});
export const { addTodo, toggleTodo, deleteTodo } = todosSlice.actions;
export default todosSlice.reducer;
// store.js
import { configureStore } from '@reduxjs/toolkit';
import todosReducer from './todosSlice';
export const store = configureStore({
reducer: {
todos: todosReducer
}
});
// 组件使用(与Redux版本相同,但actions更简洁)6. 混合使用策略
6.1 Context + Redux结合
jsx
// Context用于不常变化的全局配置
const ThemeContext = createContext();
const I18nContext = createContext();
// Redux用于频繁变化的业务状态
const store = configureStore({
reducer: {
user: userReducer,
todos: todosReducer,
posts: postsReducer
}
});
function App() {
const [theme, setTheme] = useState('light');
const [locale, setLocale] = useState('zh-CN');
return (
<Provider store={store}>
<ThemeContext.Provider value={{ theme, setTheme }}>
<I18nContext.Provider value={{ locale, setLocale }}>
<AppContent />
</I18nContext.Provider>
</ThemeContext.Provider>
</Provider>
);
}6.2 何时混合使用
typescript
const hybridStrategy = {
'使用Context': [
'主题配置',
'语言设置',
'用户认证状态',
'路由配置',
'功能开关'
],
'使用Redux': [
'业务数据(用户、订单、产品)',
'列表数据和过滤',
'表单状态',
'异步数据获取',
'需要持久化的数据'
],
'优势': [
'各司其职,职责清晰',
'性能最优',
'开发体验好',
'易于维护'
]
};7. 迁移指南
7.1 从Context迁移到Redux
jsx
// 步骤1:识别需要迁移的状态
// Context版本
const AppContext = createContext();
function AppProvider({ children }) {
const [user, setUser] = useState(null);
const [todos, setTodos] = useState([]);
const [settings, setSettings] = useState({});
return (
<AppContext.Provider value={{
user, setUser,
todos, setTodos,
settings, setSettings
}}>
{children}
</AppContext.Provider>
);
}
// 步骤2:创建Redux store
// 迁移todos到Redux
const todosSlice = createSlice({
name: 'todos',
initialState: [],
reducers: {
setTodos: (state, action) => action.payload,
addTodo: (state, action) => {
state.push(action.payload);
}
}
});
// 步骤3:保留简单状态在Context
function AppProvider({ children }) {
const [user, setUser] = useState(null);
const [settings, setSettings] = useState({});
return (
<Provider store={store}>
<UserContext.Provider value={{ user, setUser }}>
<SettingsContext.Provider value={{ settings, setSettings }}>
{children}
</SettingsContext.Provider>
</UserContext.Provider>
</Provider>
);
}
// 步骤4:更新组件
// 之前
function TodoList() {
const { todos } = useContext(AppContext);
return <ul>{/* ... */}</ul>;
}
// 之后
function TodoList() {
const todos = useSelector(state => state.todos);
return <ul>{/* ... */}</ul>;
}8. 性能测试对比
8.1 测试场景
jsx
// 测试:1000次状态更新的性能
// Context版本
function ContextPerformanceTest() {
const [count, setCount] = useState(0);
const [data, setData] = useState([]);
const runTest = () => {
const start = performance.now();
for (let i = 0; i < 1000; i++) {
setCount(i);
setData(prev => [...prev, i]);
}
const end = performance.now();
console.log(`Context: ${end - start}ms`);
};
return <button onClick={runTest}>Test Context</button>;
}
// Redux版本
function ReduxPerformanceTest() {
const dispatch = useDispatch();
const runTest = () => {
const start = performance.now();
for (let i = 0; i < 1000; i++) {
dispatch({ type: 'SET_COUNT', payload: i });
dispatch({ type: 'ADD_DATA', payload: i });
}
const end = performance.now();
console.log(`Redux: ${end - start}ms`);
};
return <button onClick={runTest}>Test Redux</button>;
}8.2 性能测试结果
typescript
const performanceComparison = {
'小型应用(<10个组件)': {
Context: '~50ms',
Redux: '~60ms',
winner: 'Context(开销更小)'
},
'中型应用(10-50个组件)': {
Context: '~200ms',
Redux: '~120ms',
winner: 'Redux(精确订阅优势开始显现)'
},
'大型应用(>50个组件)': {
Context: '~800ms',
Redux: '~150ms',
winner: 'Redux(精确订阅带来显著优势)'
},
'频繁更新场景': {
Context: '严重卡顿',
Redux: '流畅',
winner: 'Redux(批量更新、精确订阅)'
}
};9. 面试重点
9.1 高频面试题
typescript
const interviewQuestions = [
{
q: 'Context和Redux的主要区别?',
a: `
1. 性能:Redux精确订阅,Context全量通知
2. 工具:Redux有DevTools,Context没有
3. 中间件:Redux支持,Context不支持
4. 复杂度:Context简单,Redux复杂
5. 适用场景:Context适合简单场景,Redux适合复杂应用
`
},
{
q: 'Context的性能问题如何解决?',
a: `
1. 拆分Context:按更新频率分离
2. useMemo:缓存value对象
3. Context Selector:实现精确订阅
4. 考虑使用Redux或其他状态管理库
`
},
{
q: '何时选择Context?何时选择Redux?',
a: `
选择Context:
- 简单的全局状态
- 更新不频繁
- 小型应用
- 不需要调试工具
选择Redux:
- 复杂的状态逻辑
- 频繁更新
- 需要调试工具
- 需要中间件处理异步
- 大型应用
`
},
{
q: 'Context和Redux可以混合使用吗?',
a: `
可以,推荐策略:
- Context:主题、语言、认证等配置
- Redux:业务数据、异步操作
- 各司其职,发挥各自优势
`
},
{
q: 'Redux的订阅机制和Context有何不同?',
a: `
Redux:
- 组件订阅特定的state片段
- 只在订阅的数据变化时重渲染
- useSelector实现精确订阅
Context:
- 消费者订阅整个Context
- Context任何变化都会重渲染
- 需要额外优化(拆分、selector)
`
}
];9.2 实战对比表
typescript
const comparisonTable = {
'学习曲线': {
Context: '⭐⭐ 简单',
Redux: '⭐⭐⭐⭐ 复杂',
'Redux Toolkit': '⭐⭐⭐ 中等'
},
'代码量': {
Context: '⭐⭐⭐⭐⭐ 最少',
Redux: '⭐⭐ 较多',
'Redux Toolkit': '⭐⭐⭐ 中等'
},
'性能': {
Context: '⭐⭐ 需优化',
Redux: '⭐⭐⭐⭐ 优秀',
'Redux Toolkit': '⭐⭐⭐⭐ 优秀'
},
'调试体验': {
Context: '⭐⭐ 基础',
Redux: '⭐⭐⭐⭐⭐ 最佳',
'Redux Toolkit': '⭐⭐⭐⭐⭐ 最佳'
},
'异步处理': {
Context: '⭐⭐ 需自行实现',
Redux: '⭐⭐⭐⭐ 中间件支持',
'Redux Toolkit': '⭐⭐⭐⭐⭐ 内置支持'
},
'适用规模': {
Context: '小型应用',
Redux: '中大型应用',
'Redux Toolkit': '中大型应用'
}
};10. 总结
Context与Redux对比的核心要点:
- 简单 vs 强大: Context简单,Redux功能强大
- 性能: Redux订阅更精确
- 调试: Redux工具更强大
- 中间件: Redux生态丰富
- 适用场景: 根据需求选择
- 混合使用: 发挥各自优势
理解两者差异有助于选择合适方案。