对于 Vue 开发者来说,React 的状态管理可能是最需要转变思维方式的部分之一。本文将从 Vue 开发者熟悉的角度出发,详细介绍 React 的状态管理方案,并通过实战示例帮助你快速掌握。
本地状态管理对比
Vue 的响应式系统
在 Vue 中,我们习惯使用 data
选项来定义组件的本地状态:
{{ count }}
React 的 useState
而在 React 中,我们使用 useState
Hook 来管理状态:
import React, { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
const increment = () => {
setCount(count + 1); // 使用 setter 函数更新状态
};
return (
{count}
);
}
主要区别:
- Vue 的状态是响应式的,可以直接修改
- React 的状态是不可变的,必须通过 setter 函数更新
- React 的状态更新是异步的,多个更新会被批处理
复杂状态管理
使用 useReducer
当组件状态逻辑较复杂时,可以使用 useReducer
来管理状态:
import React, { useReducer } from 'react';
// 定义 reducer 函数
function todoReducer(state, action) {
switch (action.type) {
case 'ADD_TODO':
return [...state, {
id: Date.now(),
text: action.payload,
completed: false
}];
case 'TOGGLE_TODO':
return state.map(todo =>
todo.id === action.payload
? { ...todo, completed: !todo.completed }
: todo
);
case 'REMOVE_TODO':
return state.filter(todo => todo.id !== action.payload);
default:
return state;
}
}
function TodoList() {
const [todos, dispatch] = useReducer(todoReducer, []);
const [input, setInput] = useState('');
const handleAdd = () => {
if (!input.trim()) return;
dispatch({ type: 'ADD_TODO', payload: input });
setInput('');
};
return (
setInput(e.target.value)}
/>
{todos.map(todo => (
-
dispatch({
type: 'TOGGLE_TODO',
payload: todo.id
})}
/>
{todo.text}
))}
);
}
这种模式类似于 Vuex 的 mutations,但更加轻量和灵活。
全局状态管理
Context API
React 的 Context API 类似于 Vue 的 provide/inject:
// ThemeContext.js
import React, { createContext, useContext, useState } from 'react';
const ThemeContext = createContext();
export function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
const toggleTheme = () => {
setTheme(theme === 'light' ? 'dark' : 'light');
};
return (
{children}
);
}
export function useTheme() {
return useContext(ThemeContext);
}
// App.js
function App() {
return (
);
}
// Layout.js
function Layout() {
const { theme, toggleTheme } = useTheme();
return (
);
}
状态管理库对比
- Vuex vs Redux
Vuex:
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment(state) {
state.count++
}
},
actions: {
incrementAsync({ commit }) {
setTimeout(() => {
commit('increment')
}, 1000)
}
}
})
Redux:
// reducer.js
const initialState = { count: 0 };
function counterReducer(state = initialState, action) {
switch (action.type) {
case 'INCREMENT':
return { ...state, count: state.count + 1 };
default:
return state;
}
}
// actions.js
const increment = () => ({ type: 'INCREMENT' });
const incrementAsync = () => dispatch => {
setTimeout(() => {
dispatch(increment());
}, 1000);
};
// store.js
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
const store = createStore(
counterReducer,
applyMiddleware(thunk)
);
- Pinia vs Zustand
Pinia:
import { defineStore } from 'pinia';
export const useCounterStore = defineStore('counter', {
state: () => ({ count: 0 }),
actions: {
increment() {
this.count++;
}
}
});
Zustand:
import create from 'zustand';
const useStore = create(set => ({
count: 0,
increment: () => set(state => ({ count: state.count + 1 }))
}));
实战示例:购物车
让我们通过一个购物车示例来实践状态管理:
// types.ts
interface Product {
id: number;
name: string;
price: number;
}
interface CartItem extends Product {
quantity: number;
}
// cartStore.js
import create from 'zustand';
const useCartStore = create((set, get) => ({
items: [],
totalAmount: 0,
addToCart: (product) => set(state => {
const existingItem = state.items.find(item => item.id === product.id);
if (existingItem) {
return {
items: state.items.map(item =>
item.id === product.id
? { ...item, quantity: item.quantity + 1 }
: item
),
totalAmount: state.totalAmount + product.price
};
}
return {
items: [...state.items, { ...product, quantity: 1 }],
totalAmount: state.totalAmount + product.price
};
}),
removeFromCart: (productId) => set(state => {
const item = state.items.find(item => item.id === productId);
if (!item) return state;
return {
items: state.items.filter(item => item.id !== productId),
totalAmount: state.totalAmount - (item.price * item.quantity)
};
}),
updateQuantity: (productId, quantity) => set(state => {
const item = state.items.find(item => item.id === productId);
if (!item) return state;
const quantityDiff = quantity - item.quantity;
return {
items: state.items.map(item =>
item.id === productId
? { ...item, quantity }
: item
),
totalAmount: state.totalAmount + (item.price * quantityDiff)
};
})
}));
// ProductList.jsx
function ProductList() {
const [products] = useState([
{ id: 1, name: '商品1', price: 100 },
{ id: 2, name: '商品2', price: 200 },
{ id: 3, name: '商品3', price: 300 }
]);
const addToCart = useCartStore(state => state.addToCart);
return (
{products.map(product => (
{product.name}
¥{product.price}
))}
);
}
// Cart.jsx
function Cart() {
const { items, totalAmount, updateQuantity, removeFromCart } = useCartStore();
return (
购物车
{items.map(item => (
{item.name}
updateQuantity(item.id, +e.target.value)}
/>
¥{item.price * item.quantity}
))}
总计:¥{totalAmount}
);
}
性能优化
状态分割
// 不好的做法 const [state, setState] = useState({ user: null, posts: [], comments: [] }); // 好的做法 const [user, setUser] = useState(null); const [posts, setPosts] = useState([]); const [comments, setComments] = useState([]);
使用 useMemo 缓存计算结果
const totalPrice = useMemo(() => { return items.reduce((total, item) => total + item.price * item.quantity, 0); }, [items]);
使用 useCallback 缓存函数
const handleUpdate = useCallback((id, value) => { updateQuantity(id, value); }, [updateQuantity]);
避免不必要的重渲染
// CartItem.jsx const CartItem = memo(function CartItem({ item, onUpdate, onRemove }) { return (
{item.name} onUpdate(item.id, +e.target.value)} />); });
调试技巧
- 使用 React DevTools
- 查看组件树
- 检查状态变化
- 分析重渲染原因
使用 Redux DevTools
import { devtools } from 'zustand/middleware'; const useStore = create( devtools( (set) => ({ // store implementation }) ) );
使用日志中间件
const useStore = create((set) => { const originalSet = set; set = (...args) => { console.log('prev state:', get()); console.log('action:', args[0]); originalSet(...args); console.log('next state:', get()); }; return { // store implementation }; });
最佳实践
- 状态设计原则
- 保持状态最小化
- 避免冗余数据
- 合理拆分状态
- 遵循单一数据源
- 更新模式
- 使用不可变更新
- 批量处理更新
- 避免深层嵌套
- 性能考虑
- 合理使用缓存
- 避免过度订阅
- 及时清理副作用
小结
React 状态管理的特点:
- 不可变性
- 单向数据流
- 函数式更新
- 异步批处理
从 Vue 到 React 的转变:
- 告别直接修改
- 拥抱函数式
- 重视性能优化
- 合理使用 Hooks
开发建议:
- 从简单开始
- 循序渐进
- 注重实践
- 保持好奇
下一篇文章,我们将深入探讨 React 的组件设计模式,帮助你更好地组织和复用代码。
如果觉得这篇文章对你有帮助,别忘了点个赞