Appearance
useReducer实战案例
学习目标
通过本章实战案例,你将掌握:
- useReducer在真实项目中的应用
- 购物车系统的完整实现
- 表单管理系统
- 数据获取与缓存
- 游戏状态管理
- 复杂UI状态控制
- 最佳实践模式
- 性能优化技巧
- TypeScript集成
- React 19新特性应用
第一部分:购物车系统
1.1 基础购物车实现
jsx
function ShoppingCartWithReducer() {
const initialState = {
items: [],
coupon: null,
shipping: 0,
tax: 0
};
const reducer = (state, action) => {
switch (action.type) {
case 'ADD_ITEM': {
const existing = state.items.find(item => item.id === action.product.id);
if (existing) {
return {
...state,
items: state.items.map(item =>
item.id === action.product.id
? { ...item, quantity: item.quantity + 1 }
: item
)
};
}
return {
...state,
items: [...state.items, { ...action.product, quantity: 1 }]
};
}
case 'REMOVE_ITEM':
return {
...state,
items: state.items.filter(item => item.id !== action.id)
};
case 'UPDATE_QUANTITY':
if (action.quantity <= 0) {
return {
...state,
items: state.items.filter(item => item.id !== action.id)
};
}
return {
...state,
items: state.items.map(item =>
item.id === action.id
? { ...item, quantity: action.quantity }
: item
)
};
case 'APPLY_COUPON':
return {
...state,
coupon: action.coupon
};
case 'REMOVE_COUPON':
return {
...state,
coupon: null
};
case 'SET_SHIPPING':
return {
...state,
shipping: action.amount
};
case 'CLEAR_CART':
return initialState;
default:
return state;
}
};
const [state, dispatch] = useReducer(reducer, initialState);
// 计算总价
const calculations = useMemo(() => {
const subtotal = state.items.reduce(
(sum, item) => sum + item.price * item.quantity,
0
);
const discount = state.coupon
? subtotal * state.coupon.discount
: 0;
const shipping = subtotal >= 299 ? 0 : 20;
const tax = (subtotal - discount) * 0.13;
const total = subtotal - discount + shipping + tax;
return { subtotal, discount, shipping, tax, total };
}, [state.items, state.coupon]);
return (
<div className="shopping-cart">
<h2>购物车</h2>
{state.items.length === 0 ? (
<p>购物车是空的</p>
) : (
<>
<div className="cart-items">
{state.items.map(item => (
<div key={item.id} className="cart-item">
<img src={item.image} alt={item.name} />
<div>
<h4>{item.name}</h4>
<p>¥{item.price}</p>
</div>
<div className="quantity">
<button onClick={() => dispatch({
type: 'UPDATE_QUANTITY',
id: item.id,
quantity: item.quantity - 1
})}>
-
</button>
<span>{item.quantity}</span>
<button onClick={() => dispatch({
type: 'UPDATE_QUANTITY',
id: item.id,
quantity: item.quantity + 1
})}>
+
</button>
</div>
<button onClick={() => dispatch({
type: 'REMOVE_ITEM',
id: item.id
})}>
删除
</button>
</div>
))}
</div>
<div className="summary">
<div>小计: ¥{calculations.subtotal.toFixed(2)}</div>
{calculations.discount > 0 && (
<div>优惠: -¥{calculations.discount.toFixed(2)}</div>
)}
<div>运费: ¥{calculations.shipping.toFixed(2)}</div>
<div>税费: ¥{calculations.tax.toFixed(2)}</div>
<div className="total">
总计: ¥{calculations.total.toFixed(2)}
</div>
</div>
<button onClick={() => dispatch({ type: 'CLEAR_CART' })}>
清空购物车
</button>
</>
)}
</div>
);
}1.2 高级购物车功能
添加更多功能:库存管理、愿望清单、历史订单等。
jsx
function AdvancedShoppingCart() {
const initialState = {
items: [],
wishlist: [],
orders: [],
coupons: [],
selectedCoupon: null,
shippingAddress: null,
paymentMethod: null,
inventory: {},
ui: {
showWishlist: false,
showHistory: false,
checkoutStep: 0
}
};
const reducer = (state, action) => {
switch (action.type) {
case 'ADD_ITEM': {
const { product, quantity = 1 } = action;
const currentStock = state.inventory[product.id]?.stock || 0;
const currentInCart = state.items.find(item => item.id === product.id)?.quantity || 0;
if (currentInCart + quantity > currentStock) {
return state; // 库存不足
}
const existing = state.items.find(item => item.id === product.id);
if (existing) {
return {
...state,
items: state.items.map(item =>
item.id === product.id
? { ...item, quantity: item.quantity + quantity }
: item
)
};
}
return {
...state,
items: [...state.items, { ...product, quantity }]
};
}
case 'REMOVE_ITEM':
return {
...state,
items: state.items.filter(item => item.id !== action.id)
};
case 'UPDATE_QUANTITY': {
const { id, quantity } = action;
const product = state.items.find(item => item.id === id);
const stock = state.inventory[id]?.stock || 0;
if (quantity > stock) {
return state;
}
if (quantity <= 0) {
return {
...state,
items: state.items.filter(item => item.id !== id)
};
}
return {
...state,
items: state.items.map(item =>
item.id === id ? { ...item, quantity } : item
)
};
}
case 'ADD_TO_WISHLIST': {
const existing = state.wishlist.find(item => item.id === action.product.id);
if (existing) {
return state;
}
return {
...state,
wishlist: [...state.wishlist, action.product]
};
}
case 'REMOVE_FROM_WISHLIST':
return {
...state,
wishlist: state.wishlist.filter(item => item.id !== action.id)
};
case 'MOVE_TO_CART': {
const item = state.wishlist.find(w => w.id === action.id);
if (!item) {
return state;
}
return reducer(
reducer(state, { type: 'REMOVE_FROM_WISHLIST', id: action.id }),
{ type: 'ADD_ITEM', product: item }
);
}
case 'APPLY_COUPON': {
const coupon = state.coupons.find(c => c.code === action.code);
if (!coupon || !coupon.isValid) {
return state;
}
return {
...state,
selectedCoupon: coupon
};
}
case 'REMOVE_COUPON':
return {
...state,
selectedCoupon: null
};
case 'SET_SHIPPING_ADDRESS':
return {
...state,
shippingAddress: action.address
};
case 'SET_PAYMENT_METHOD':
return {
...state,
paymentMethod: action.method
};
case 'UPDATE_INVENTORY':
return {
...state,
inventory: {
...state.inventory,
[action.productId]: action.inventory
}
};
case 'PLACE_ORDER': {
const order = {
id: Date.now(),
items: state.items,
total: action.total,
date: new Date().toISOString(),
status: 'pending'
};
return {
...initialState,
orders: [...state.orders, order],
inventory: state.inventory
};
}
case 'UPDATE_UI':
return {
...state,
ui: {
...state.ui,
...action.updates
}
};
case 'NEXT_CHECKOUT_STEP':
return {
...state,
ui: {
...state.ui,
checkoutStep: Math.min(state.ui.checkoutStep + 1, 3)
}
};
case 'PREV_CHECKOUT_STEP':
return {
...state,
ui: {
...state.ui,
checkoutStep: Math.max(state.ui.checkoutStep - 1, 0)
}
};
case 'CLEAR_CART':
return {
...state,
items: []
};
default:
return state;
}
};
const [state, dispatch] = useReducer(reducer, initialState);
const calculations = useMemo(() => {
const subtotal = state.items.reduce(
(sum, item) => sum + item.price * item.quantity,
0
);
let discount = 0;
if (state.selectedCoupon) {
if (state.selectedCoupon.type === 'percentage') {
discount = subtotal * (state.selectedCoupon.value / 100);
} else if (state.selectedCoupon.type === 'fixed') {
discount = Math.min(state.selectedCoupon.value, subtotal);
}
}
const shipping = subtotal >= 299 ? 0 : 20;
const tax = (subtotal - discount) * 0.13;
const total = subtotal - discount + shipping + tax;
return { subtotal, discount, shipping, tax, total };
}, [state.items, state.selectedCoupon]);
const handleCheckout = () => {
dispatch({ type: 'PLACE_ORDER', total: calculations.total });
alert('订单已提交!');
};
return (
<div className="advanced-cart">
<h2>高级购物车</h2>
<div className="tabs">
<button onClick={() => dispatch({ type: 'UPDATE_UI', updates: { showWishlist: false, showHistory: false } })}>
购物车 ({state.items.length})
</button>
<button onClick={() => dispatch({ type: 'UPDATE_UI', updates: { showWishlist: true, showHistory: false } })}>
愿望清单 ({state.wishlist.length})
</button>
<button onClick={() => dispatch({ type: 'UPDATE_UI', updates: { showWishlist: false, showHistory: true } })}>
历史订单 ({state.orders.length})
</button>
</div>
{!state.ui.showWishlist && !state.ui.showHistory && (
<>
{state.items.length === 0 ? (
<p>购物车是空的</p>
) : (
<>
<div className="cart-items">
{state.items.map(item => (
<div key={item.id} className="cart-item">
<img src={item.image} alt={item.name} />
<div>
<h4>{item.name}</h4>
<p>¥{item.price}</p>
<p className="stock">
库存: {state.inventory[item.id]?.stock || 0}
</p>
</div>
<div className="quantity">
<button onClick={() => dispatch({
type: 'UPDATE_QUANTITY',
id: item.id,
quantity: item.quantity - 1
})}>
-
</button>
<span>{item.quantity}</span>
<button onClick={() => dispatch({
type: 'UPDATE_QUANTITY',
id: item.id,
quantity: item.quantity + 1
})}>
+
</button>
</div>
<button onClick={() => dispatch({
type: 'REMOVE_ITEM',
id: item.id
})}>
删除
</button>
<button onClick={() => dispatch({
type: 'ADD_TO_WISHLIST',
product: item
})}>
加入愿望清单
</button>
</div>
))}
</div>
<div className="summary">
<div>小计: ¥{calculations.subtotal.toFixed(2)}</div>
{calculations.discount > 0 && (
<div>优惠: -¥{calculations.discount.toFixed(2)}</div>
)}
<div>运费: ¥{calculations.shipping.toFixed(2)}</div>
<div>税费: ¥{calculations.tax.toFixed(2)}</div>
<div className="total">
总计: ¥{calculations.total.toFixed(2)}
</div>
</div>
<button onClick={handleCheckout}>
结账
</button>
</>
)}
</>
)}
{state.ui.showWishlist && (
<div className="wishlist">
<h3>愿望清单</h3>
{state.wishlist.map(item => (
<div key={item.id} className="wishlist-item">
<img src={item.image} alt={item.name} />
<div>
<h4>{item.name}</h4>
<p>¥{item.price}</p>
</div>
<button onClick={() => dispatch({ type: 'MOVE_TO_CART', id: item.id })}>
加入购物车
</button>
<button onClick={() => dispatch({ type: 'REMOVE_FROM_WISHLIST', id: item.id })}>
删除
</button>
</div>
))}
</div>
)}
{state.ui.showHistory && (
<div className="order-history">
<h3>历史订单</h3>
{state.orders.map(order => (
<div key={order.id} className="order">
<h4>订单 #{order.id}</h4>
<p>日期: {new Date(order.date).toLocaleDateString()}</p>
<p>总计: ¥{order.total.toFixed(2)}</p>
<p>状态: {order.status}</p>
</div>
))}
</div>
)}
</div>
);
}1.3 购物车持久化
使用localStorage保存购物车数据。
jsx
function PersistentShoppingCart() {
const initialState = {
items: [],
coupon: null
};
const init = (initialValue) => {
try {
const saved = localStorage.getItem('shopping-cart');
return saved ? JSON.parse(saved) : initialValue;
} catch {
return initialValue;
}
};
const reducer = (state, action) => {
// ... reducer逻辑同上
switch (action.type) {
case 'ADD_ITEM':
case 'REMOVE_ITEM':
case 'UPDATE_QUANTITY':
case 'APPLY_COUPON':
case 'REMOVE_COUPON':
case 'CLEAR_CART':
// 执行状态更新逻辑
const newState = {
...state,
// ... 更新逻辑
};
// 保存到localStorage
localStorage.setItem('shopping-cart', JSON.stringify(newState));
return newState;
default:
return state;
}
};
const [state, dispatch] = useReducer(reducer, initialState, init);
useEffect(() => {
localStorage.setItem('shopping-cart', JSON.stringify(state));
}, [state]);
return (
<div>
{/* UI组件 */}
</div>
);
}第二部分:表单管理系统
2.1 基础表单管理
jsx
function ComplexFormWithReducer() {
const initialState = {
fields: {
personal: {
firstName: '',
lastName: '',
email: '',
phone: ''
},
address: {
street: '',
city: '',
state: '',
zipCode: ''
},
preferences: {
newsletter: false,
notifications: true
}
},
validation: {
errors: {},
touched: {}
},
submission: {
loading: false,
success: false,
error: null
}
};
const reducer = (state, action) => {
switch (action.type) {
case 'UPDATE_FIELD':
const { section, field, value } = action;
return {
...state,
fields: {
...state.fields,
[section]: {
...state.fields[section],
[field]: value
}
}
};
case 'SET_TOUCHED':
return {
...state,
validation: {
...state.validation,
touched: {
...state.validation.touched,
[`${action.section}.${action.field}`]: true
}
}
};
case 'SET_ERROR':
return {
...state,
validation: {
...state.validation,
errors: {
...state.validation.errors,
[`${action.section}.${action.field}`]: action.error
}
}
};
case 'SET_ERRORS':
return {
...state,
validation: {
...state.validation,
errors: action.errors
}
};
case 'SUBMIT_START':
return {
...state,
submission: {
loading: true,
success: false,
error: null
}
};
case 'SUBMIT_SUCCESS':
return {
...initialState,
submission: {
loading: false,
success: true,
error: null
}
};
case 'SUBMIT_ERROR':
return {
...state,
submission: {
loading: false,
success: false,
error: action.error
}
};
case 'RESET':
return initialState;
default:
return state;
}
};
const [state, dispatch] = useReducer(reducer, initialState);
const handleFieldChange = (section, field) => (e) => {
const value = e.target.type === 'checkbox' ? e.target.checked : e.target.value;
dispatch({ type: 'UPDATE_FIELD', section, field, value });
};
const handleBlur = (section, field) => () => {
dispatch({ type: 'SET_TOUCHED', section, field });
// 验证字段
const value = state.fields[section][field];
const error = validateField(field, value);
if (error) {
dispatch({ type: 'SET_ERROR', section, field, error });
}
};
const validateField = (field, value) => {
switch (field) {
case 'email':
return !value.includes('@') ? '邮箱格式不正确' : null;
case 'phone':
return !/^\d{11}$/.test(value) ? '手机号格式不正确' : null;
case 'zipCode':
return !/^\d{6}$/.test(value) ? '邮编格式不正确' : null;
default:
return !value ? '此字段为必填项' : null;
}
};
const handleSubmit = async (e) => {
e.preventDefault();
// 验证所有字段
const errors = {};
Object.entries(state.fields).forEach(([section, fields]) => {
Object.entries(fields).forEach(([field, value]) => {
const error = validateField(field, value);
if (error) {
errors[`${section}.${field}`] = error;
}
});
});
if (Object.keys(errors).length > 0) {
dispatch({ type: 'SET_ERRORS', errors });
return;
}
dispatch({ type: 'SUBMIT_START' });
try {
await submitForm(state.fields);
dispatch({ type: 'SUBMIT_SUCCESS' });
} catch (error) {
dispatch({ type: 'SUBMIT_ERROR', error: error.message });
}
};
return (
<form onSubmit={handleSubmit}>
<section>
<h3>个人信息</h3>
<div>
<input
value={state.fields.personal.firstName}
onChange={handleFieldChange('personal', 'firstName')}
onBlur={handleBlur('personal', 'firstName')}
placeholder="名"
/>
{state.validation.errors['personal.firstName'] && (
<span className="error">
{state.validation.errors['personal.firstName']}
</span>
)}
</div>
<div>
<input
value={state.fields.personal.lastName}
onChange={handleFieldChange('personal', 'lastName')}
onBlur={handleBlur('personal', 'lastName')}
placeholder="姓"
/>
{state.validation.errors['personal.lastName'] && (
<span className="error">
{state.validation.errors['personal.lastName']}
</span>
)}
</div>
<div>
<input
type="email"
value={state.fields.personal.email}
onChange={handleFieldChange('personal', 'email')}
onBlur={handleBlur('personal', 'email')}
placeholder="邮箱"
/>
{state.validation.errors['personal.email'] && (
<span className="error">
{state.validation.errors['personal.email']}
</span>
)}
</div>
<div>
<input
value={state.fields.personal.phone}
onChange={handleFieldChange('personal', 'phone')}
onBlur={handleBlur('personal', 'phone')}
placeholder="电话"
/>
{state.validation.errors['personal.phone'] && (
<span className="error">
{state.validation.errors['personal.phone']}
</span>
)}
</div>
</section>
<section>
<h3>地址信息</h3>
<div>
<input
value={state.fields.address.street}
onChange={handleFieldChange('address', 'street')}
onBlur={handleBlur('address', 'street')}
placeholder="街道"
/>
</div>
<div>
<input
value={state.fields.address.city}
onChange={handleFieldChange('address', 'city')}
onBlur={handleBlur('address', 'city')}
placeholder="城市"
/>
</div>
<div>
<input
value={state.fields.address.state}
onChange={handleFieldChange('address', 'state')}
onBlur={handleBlur('address', 'state')}
placeholder="省份"
/>
</div>
<div>
<input
value={state.fields.address.zipCode}
onChange={handleFieldChange('address', 'zipCode')}
onBlur={handleBlur('address', 'zipCode')}
placeholder="邮编"
/>
{state.validation.errors['address.zipCode'] && (
<span className="error">
{state.validation.errors['address.zipCode']}
</span>
)}
</div>
</section>
<section>
<h3>偏好设置</h3>
<div>
<label>
<input
type="checkbox"
checked={state.fields.preferences.newsletter}
onChange={handleFieldChange('preferences', 'newsletter')}
/>
订阅新闻通讯
</label>
</div>
<div>
<label>
<input
type="checkbox"
checked={state.fields.preferences.notifications}
onChange={handleFieldChange('preferences', 'notifications')}
/>
接收通知
</label>
</div>
</section>
<button type="submit" disabled={state.submission.loading}>
{state.submission.loading ? '提交中...' : '提交'}
</button>
{state.submission.success && <p className="success">提交成功!</p>}
{state.submission.error && <p className="error">{state.submission.error}</p>}
</form>
);
}
async function submitForm(data) {
// 模拟API调用
return new Promise((resolve) => {
setTimeout(() => {
console.log('表单数据:', data);
resolve();
}, 1000);
});
}2.2 多步骤表单
实现向导式多步骤表单。
jsx
function MultiStepForm() {
const initialState = {
currentStep: 0,
data: {
step1: { username: '', email: '' },
step2: { address: '', city: '' },
step3: { cardNumber: '', cvv: '' }
},
validation: {
errors: {},
canProceed: false
},
submission: {
loading: false,
success: false,
error: null
}
};
const reducer = (state, action) => {
switch (action.type) {
case 'UPDATE_STEP_DATA':
return {
...state,
data: {
...state.data,
[action.step]: {
...state.data[action.step],
...action.data
}
}
};
case 'NEXT_STEP':
return {
...state,
currentStep: Math.min(state.currentStep + 1, 2)
};
case 'PREV_STEP':
return {
...state,
currentStep: Math.max(state.currentStep - 1, 0)
};
case 'GO_TO_STEP':
return {
...state,
currentStep: action.step
};
case 'SET_VALIDATION':
return {
...state,
validation: {
...state.validation,
...action.validation
}
};
case 'SUBMIT_START':
return {
...state,
submission: {
loading: true,
success: false,
error: null
}
};
case 'SUBMIT_SUCCESS':
return {
...initialState,
submission: {
loading: false,
success: true,
error: null
}
};
case 'SUBMIT_ERROR':
return {
...state,
submission: {
loading: false,
success: false,
error: action.error
}
};
default:
return state;
}
};
const [state, dispatch] = useReducer(reducer, initialState);
const steps = [
{ title: '账户信息', component: Step1 },
{ title: '地址信息', component: Step2 },
{ title: '支付信息', component: Step3 }
];
const CurrentStepComponent = steps[state.currentStep].component;
const handleNext = () => {
dispatch({ type: 'NEXT_STEP' });
};
const handlePrev = () => {
dispatch({ type: 'PREV_STEP' });
};
const handleSubmit = async () => {
dispatch({ type: 'SUBMIT_START' });
try {
await submitMultiStepForm(state.data);
dispatch({ type: 'SUBMIT_SUCCESS' });
} catch (error) {
dispatch({ type: 'SUBMIT_ERROR', error: error.message });
}
};
return (
<div className="multi-step-form">
<div className="steps-indicator">
{steps.map((step, index) => (
<div
key={index}
className={`step ${index === state.currentStep ? 'active' : ''} ${
index < state.currentStep ? 'completed' : ''
}`}
onClick={() => dispatch({ type: 'GO_TO_STEP', step: index })}
>
<div className="step-number">{index + 1}</div>
<div className="step-title">{step.title}</div>
</div>
))}
</div>
<div className="step-content">
<CurrentStepComponent
data={state.data[`step${state.currentStep + 1}`]}
onChange={(data) => dispatch({
type: 'UPDATE_STEP_DATA',
step: `step${state.currentStep + 1}`,
data
})}
/>
</div>
<div className="navigation">
{state.currentStep > 0 && (
<button onClick={handlePrev}>上一步</button>
)}
{state.currentStep < steps.length - 1 ? (
<button onClick={handleNext}>下一步</button>
) : (
<button onClick={handleSubmit} disabled={state.submission.loading}>
{state.submission.loading ? '提交中...' : '提交'}
</button>
)}
</div>
{state.submission.success && <p className="success">提交成功!</p>}
{state.submission.error && <p className="error">{state.submission.error}</p>}
</div>
);
}
function Step1({ data, onChange }) {
return (
<div>
<h3>账户信息</h3>
<input
value={data.username}
onChange={(e) => onChange({ username: e.target.value })}
placeholder="用户名"
/>
<input
type="email"
value={data.email}
onChange={(e) => onChange({ email: e.target.value })}
placeholder="邮箱"
/>
</div>
);
}
function Step2({ data, onChange }) {
return (
<div>
<h3>地址信息</h3>
<input
value={data.address}
onChange={(e) => onChange({ address: e.target.value })}
placeholder="地址"
/>
<input
value={data.city}
onChange={(e) => onChange({ city: e.target.value })}
placeholder="城市"
/>
</div>
);
}
function Step3({ data, onChange }) {
return (
<div>
<h3>支付信息</h3>
<input
value={data.cardNumber}
onChange={(e) => onChange({ cardNumber: e.target.value })}
placeholder="卡号"
/>
<input
value={data.cvv}
onChange={(e) => onChange({ cvv: e.target.value })}
placeholder="CVV"
/>
</div>
);
}
async function submitMultiStepForm(data) {
return new Promise((resolve) => {
setTimeout(() => {
console.log('多步骤表单数据:', data);
resolve();
}, 1000);
});
}第三部分:数据获取与缓存
3.1 基础数据获取
jsx
function DataFetchingWithReducer({ url }) {
const initialState = {
data: null,
loading: false,
error: null,
refetchIndex: 0
};
const reducer = (state, action) => {
switch (action.type) {
case 'FETCH_START':
return {
...state,
loading: true,
error: null
};
case 'FETCH_SUCCESS':
return {
...state,
loading: false,
data: action.payload,
error: null
};
case 'FETCH_ERROR':
return {
...state,
loading: false,
error: action.error
};
case 'REFETCH':
return {
...state,
refetchIndex: state.refetchIndex + 1
};
default:
return state;
}
};
const [state, dispatch] = useReducer(reducer, initialState);
useEffect(() => {
let cancelled = false;
dispatch({ type: 'FETCH_START' });
fetch(url)
.then(res => res.json())
.then(data => {
if (!cancelled) {
dispatch({ type: 'FETCH_SUCCESS', payload: data });
}
})
.catch(error => {
if (!cancelled) {
dispatch({ type: 'FETCH_ERROR', error: error.message });
}
});
return () => {
cancelled = true;
};
}, [url, state.refetchIndex]);
if (state.loading) return <div>加载中...</div>;
if (state.error) return (
<div>
<p>错误: {state.error}</p>
<button onClick={() => dispatch({ type: 'REFETCH' })}>重试</button>
</div>
);
if (!state.data) return null;
return (
<div>
<pre>{JSON.stringify(state.data, null, 2)}</pre>
<button onClick={() => dispatch({ type: 'REFETCH' })}>
刷新数据
</button>
</div>
);
}3.2 高级数据获取(带缓存、分页、过滤)
jsx
function AdvancedDataFetching() {
const initialState = {
data: [],
cache: {},
loading: false,
error: null,
pagination: {
page: 1,
pageSize: 10,
total: 0
},
filters: {
search: '',
category: 'all',
sortBy: 'name',
sortOrder: 'asc'
}
};
const reducer = (state, action) => {
switch (action.type) {
case 'FETCH_START':
return {
...state,
loading: true,
error: null
};
case 'FETCH_SUCCESS': {
const cacheKey = `${action.page}_${action.filters}`;
return {
...state,
loading: false,
data: action.payload.data,
cache: {
...state.cache,
[cacheKey]: action.payload.data
},
pagination: {
...state.pagination,
total: action.payload.total
}
};
}
case 'FETCH_ERROR':
return {
...state,
loading: false,
error: action.error
};
case 'SET_PAGE':
return {
...state,
pagination: {
...state.pagination,
page: action.page
}
};
case 'SET_FILTER':
return {
...state,
filters: {
...state.filters,
[action.key]: action.value
},
pagination: {
...state.pagination,
page: 1 // 重置到第一页
}
};
case 'CLEAR_CACHE':
return {
...state,
cache: {}
};
default:
return state;
}
};
const [state, dispatch] = useReducer(reducer, initialState);
useEffect(() => {
const cacheKey = `${state.pagination.page}_${JSON.stringify(state.filters)}`;
// 检查缓存
if (state.cache[cacheKey]) {
return;
}
let cancelled = false;
dispatch({ type: 'FETCH_START' });
const fetchData = async () => {
try {
const params = new URLSearchParams({
page: state.pagination.page,
pageSize: state.pagination.pageSize,
...state.filters
});
const response = await fetch(`/api/data?${params}`);
const result = await response.json();
if (!cancelled) {
dispatch({
type: 'FETCH_SUCCESS',
payload: result,
page: state.pagination.page,
filters: JSON.stringify(state.filters)
});
}
} catch (error) {
if (!cancelled) {
dispatch({ type: 'FETCH_ERROR', error: error.message });
}
}
};
fetchData();
return () => {
cancelled = true;
};
}, [state.pagination.page, state.filters]);
return (
<div>
<div className="filters">
<input
type="search"
value={state.filters.search}
onChange={(e) => dispatch({
type: 'SET_FILTER',
key: 'search',
value: e.target.value
})}
placeholder="搜索..."
/>
<select
value={state.filters.category}
onChange={(e) => dispatch({
type: 'SET_FILTER',
key: 'category',
value: e.target.value
})}
>
<option value="all">全部分类</option>
<option value="tech">技术</option>
<option value="design">设计</option>
</select>
<select
value={state.filters.sortBy}
onChange={(e) => dispatch({
type: 'SET_FILTER',
key: 'sortBy',
value: e.target.value
})}
>
<option value="name">名称</option>
<option value="date">日期</option>
<option value="price">价格</option>
</select>
<button onClick={() => dispatch({ type: 'CLEAR_CACHE' })}>
清除缓存
</button>
</div>
{state.loading && <div>加载中...</div>}
{state.error && <div>错误: {state.error}</div>}
{state.data.length > 0 && (
<>
<div className="data-list">
{state.data.map(item => (
<div key={item.id} className="data-item">
<h3>{item.name}</h3>
<p>{item.description}</p>
</div>
))}
</div>
<div className="pagination">
<button
disabled={state.pagination.page === 1}
onClick={() => dispatch({
type: 'SET_PAGE',
page: state.pagination.page - 1
})}
>
上一页
</button>
<span>
第 {state.pagination.page} 页,共 {Math.ceil(state.pagination.total / state.pagination.pageSize)} 页
</span>
<button
disabled={state.pagination.page >= Math.ceil(state.pagination.total / state.pagination.pageSize)}
onClick={() => dispatch({
type: 'SET_PAGE',
page: state.pagination.page + 1
})}
>
下一页
</button>
</div>
</>
)}
</div>
);
}第四部分:游戏状态管理
4.1 井字棋游戏
jsx
function TicTacToeGame() {
const initialState = {
board: Array(9).fill(null),
xIsNext: true,
winner: null,
winningLine: null,
history: [],
currentMove: 0
};
const reducer = (state, action) => {
switch (action.type) {
case 'MAKE_MOVE': {
const { index } = action;
if (state.board[index] || state.winner) {
return state;
}
const newBoard = [...state.board];
newBoard[index] = state.xIsNext ? 'X' : 'O';
const winner = calculateWinner(newBoard);
return {
...state,
board: newBoard,
xIsNext: !state.xIsNext,
winner: winner?.player || null,
winningLine: winner?.line || null,
history: [
...state.history.slice(0, state.currentMove + 1),
{ board: newBoard, move: index }
],
currentMove: state.currentMove + 1
};
}
case 'JUMP_TO_MOVE':
const move = state.history[action.move];
return {
...state,
board: move.board,
xIsNext: action.move % 2 === 0,
currentMove: action.move,
winner: calculateWinner(move.board)?.player || null
};
case 'RESET':
return initialState;
default:
return state;
}
};
const [state, dispatch] = useReducer(reducer, initialState);
const status = state.winner
? `获胜者: ${state.winner}`
: `下一位: ${state.xIsNext ? 'X' : 'O'}`;
return (
<div className="game">
<div className="game-board">
<div className="status">{status}</div>
<div className="board">
{state.board.map((cell, i) => (
<button
key={i}
className={`cell ${state.winningLine?.includes(i) ? 'winning' : ''}`}
onClick={() => dispatch({ type: 'MAKE_MOVE', index: i })}
>
{cell}
</button>
))}
</div>
<button onClick={() => dispatch({ type: 'RESET' })}>
重新开始
</button>
</div>
<div className="game-history">
<h3>历史记录</h3>
<ol>
{state.history.map((_, index) => (
<li key={index}>
<button onClick={() => dispatch({ type: 'JUMP_TO_MOVE', move: index })}>
{index === 0 ? '游戏开始' : `移动 #${index}`}
</button>
</li>
))}
</ol>
</div>
</div>
);
}
function calculateWinner(board) {
const lines = [
[0, 1, 2], [3, 4, 5], [6, 7, 8],
[0, 3, 6], [1, 4, 7], [2, 5, 8],
[0, 4, 8], [2, 4, 6]
];
for (const line of lines) {
const [a, b, c] = line;
if (board[a] && board[a] === board[b] && board[a] === board[c]) {
return { player: board[a], line };
}
}
return null;
}4.2 俄罗斯方块游戏
jsx
function TetrisGame() {
const BOARD_WIDTH = 10;
const BOARD_HEIGHT = 20;
const createEmptyBoard = () =>
Array(BOARD_HEIGHT).fill(null).map(() => Array(BOARD_WIDTH).fill(0));
const initialState = {
board: createEmptyBoard(),
currentPiece: null,
nextPiece: null,
position: { x: 0, y: 0 },
score: 0,
level: 1,
lines: 0,
gameOver: false,
isPaused: false
};
const reducer = (state, action) => {
switch (action.type) {
case 'START_GAME':
return {
...initialState,
currentPiece: getRandomPiece(),
nextPiece: getRandomPiece(),
position: { x: BOARD_WIDTH / 2 - 1, y: 0 }
};
case 'MOVE_LEFT': {
if (state.gameOver || state.isPaused) return state;
const newPosition = { ...state.position, x: state.position.x - 1 };
if (canMove(state.board, state.currentPiece, newPosition)) {
return {
...state,
position: newPosition
};
}
return state;
}
case 'MOVE_RIGHT': {
if (state.gameOver || state.isPaused) return state;
const newPosition = { ...state.position, x: state.position.x + 1 };
if (canMove(state.board, state.currentPiece, newPosition)) {
return {
...state,
position: newPosition
};
}
return state;
}
case 'MOVE_DOWN': {
if (state.gameOver || state.isPaused) return state;
const newPosition = { ...state.position, y: state.position.y + 1 };
if (canMove(state.board, state.currentPiece, newPosition)) {
return {
...state,
position: newPosition
};
}
// 不能移动,放置方块
const newBoard = placePiece(
state.board,
state.currentPiece,
state.position
);
const { board: clearedBoard, linesCleared } = clearLines(newBoard);
const newScore = state.score + calculateScore(linesCleared, state.level);
const newLines = state.lines + linesCleared;
const newLevel = Math.floor(newLines / 10) + 1;
// 检查游戏是否结束
const gameOver = state.position.y <= 0;
return {
...state,
board: clearedBoard,
currentPiece: state.nextPiece,
nextPiece: getRandomPiece(),
position: { x: BOARD_WIDTH / 2 - 1, y: 0 },
score: newScore,
lines: newLines,
level: newLevel,
gameOver
};
}
case 'ROTATE': {
if (state.gameOver || state.isPaused) return state;
const rotatedPiece = rotatePiece(state.currentPiece);
if (canMove(state.board, rotatedPiece, state.position)) {
return {
...state,
currentPiece: rotatedPiece
};
}
return state;
}
case 'HARD_DROP': {
if (state.gameOver || state.isPaused) return state;
let newPosition = { ...state.position };
while (canMove(state.board, state.currentPiece, { ...newPosition, y: newPosition.y + 1 })) {
newPosition.y++;
}
return reducer(
{ ...state, position: newPosition },
{ type: 'MOVE_DOWN' }
);
}
case 'TOGGLE_PAUSE':
return {
...state,
isPaused: !state.isPaused
};
case 'RESET':
return initialState;
default:
return state;
}
};
const [state, dispatch] = useReducer(reducer, initialState);
useEffect(() => {
if (!state.currentPiece || state.gameOver || state.isPaused) {
return;
}
const interval = setInterval(() => {
dispatch({ type: 'MOVE_DOWN' });
}, 1000 - (state.level - 1) * 100);
return () => clearInterval(interval);
}, [state.currentPiece, state.gameOver, state.isPaused, state.level]);
useEffect(() => {
const handleKeyPress = (e) => {
if (state.gameOver) return;
switch (e.key) {
case 'ArrowLeft':
dispatch({ type: 'MOVE_LEFT' });
break;
case 'ArrowRight':
dispatch({ type: 'MOVE_RIGHT' });
break;
case 'ArrowDown':
dispatch({ type: 'MOVE_DOWN' });
break;
case 'ArrowUp':
dispatch({ type: 'ROTATE' });
break;
case ' ':
dispatch({ type: 'HARD_DROP' });
break;
case 'p':
dispatch({ type: 'TOGGLE_PAUSE' });
break;
}
};
window.addEventListener('keydown', handleKeyPress);
return () => window.removeEventListener('keydown', handleKeyPress);
}, [state.gameOver]);
return (
<div className="tetris">
<div className="game-info">
<div>分数: {state.score}</div>
<div>等级: {state.level}</div>
<div>行数: {state.lines}</div>
{state.gameOver && <div className="game-over">游戏结束</div>}
{state.isPaused && <div className="paused">暂停</div>}
</div>
<div className="game-board">
{renderBoard(state.board, state.currentPiece, state.position)}
</div>
<div className="controls">
<button onClick={() => dispatch({ type: 'START_GAME' })}>
开始游戏
</button>
<button onClick={() => dispatch({ type: 'TOGGLE_PAUSE' })}>
{state.isPaused ? '继续' : '暂停'}
</button>
<button onClick={() => dispatch({ type: 'RESET' })}>
重置
</button>
</div>
<div className="next-piece">
<h3>下一个</h3>
{state.nextPiece && renderPiece(state.nextPiece)}
</div>
</div>
);
}
// 辅助函数
function getRandomPiece() {
const pieces = ['I', 'O', 'T', 'S', 'Z', 'J', 'L'];
return pieces[Math.floor(Math.random() * pieces.length)];
}
function canMove(board, piece, position) {
// 实现碰撞检测逻辑
return true; // 简化示例
}
function placePiece(board, piece, position) {
// 实现放置方块逻辑
return board; // 简化示例
}
function clearLines(board) {
// 实现清除完整行逻辑
return { board, linesCleared: 0 }; // 简化示例
}
function calculateScore(lines, level) {
const scores = [0, 100, 300, 500, 800];
return scores[lines] * level;
}
function rotatePiece(piece) {
// 实现方块旋转逻辑
return piece; // 简化示例
}
function renderBoard(board, currentPiece, position) {
// 实现渲染逻辑
return <div className="board">Board</div>; // 简化示例
}
function renderPiece(piece) {
// 实现渲染方块逻辑
return <div className="piece">Piece</div>; // 简化示例
}第五部分:结合Context全局状态
5.1 应用级状态管理
jsx
const AppContext = createContext(null);
function AppProvider({ children }) {
const initialState = {
user: null,
theme: 'light',
language: 'zh-CN',
notifications: [],
settings: {
autoSave: true,
soundEnabled: true
}
};
const reducer = (state, action) => {
switch (action.type) {
case 'LOGIN':
return {
...state,
user: action.user
};
case 'LOGOUT':
return {
...state,
user: null
};
case 'SET_THEME':
return {
...state,
theme: action.theme
};
case 'SET_LANGUAGE':
return {
...state,
language: action.language
};
case 'ADD_NOTIFICATION':
return {
...state,
notifications: [
...state.notifications,
{ id: Date.now(), ...action.notification }
]
};
case 'REMOVE_NOTIFICATION':
return {
...state,
notifications: state.notifications.filter(n => n.id !== action.id)
};
case 'UPDATE_SETTINGS':
return {
...state,
settings: {
...state.settings,
...action.settings
}
};
default:
return state;
}
};
const [state, dispatch] = useReducer(reducer, initialState);
const contextValue = useMemo(() => ({ state, dispatch }), [state]);
return (
<AppContext.Provider value={contextValue}>
{children}
</AppContext.Provider>
);
}
function useApp() {
const context = useContext(AppContext);
if (!context) {
throw new Error('useApp必须在AppProvider内使用');
}
return context;
}
// 在任何组件中使用
function UserMenu() {
const { state, dispatch } = useApp();
const handleLogout = () => {
dispatch({ type: 'LOGOUT' });
};
return (
<div>
{state.user ? (
<>
<span>欢迎, {state.user.name}</span>
<button onClick={handleLogout}>退出</button>
</>
) : (
<button onClick={() => {/* ... */}}>登录</button>
)}
</div>
);
}
function ThemeToggle() {
const { state, dispatch } = useApp();
const toggleTheme = () => {
const newTheme = state.theme === 'light' ? 'dark' : 'light';
dispatch({ type: 'SET_THEME', theme: newTheme });
};
return (
<button onClick={toggleTheme}>
切换到{state.theme === 'light' ? '深色' : '浅色'}模式
</button>
);
}
function NotificationCenter() {
const { state, dispatch } = useApp();
useEffect(() => {
// 自动清除过期通知
const interval = setInterval(() => {
const now = Date.now();
state.notifications.forEach(notification => {
if (notification.expiresAt && notification.expiresAt < now) {
dispatch({ type: 'REMOVE_NOTIFICATION', id: notification.id });
}
});
}, 1000);
return () => clearInterval(interval);
}, [state.notifications, dispatch]);
return (
<div className="notification-center">
{state.notifications.map(notification => (
<div key={notification.id} className={`notification ${notification.type}`}>
<span>{notification.message}</span>
<button onClick={() => dispatch({
type: 'REMOVE_NOTIFICATION',
id: notification.id
})}>
关闭
</button>
</div>
))}
</div>
);
}第六部分:TypeScript集成
6.1 类型安全的Reducer
typescript
type CartItem = {
id: number;
name: string;
price: number;
quantity: number;
};
type CartState = {
items: CartItem[];
coupon: {
code: string;
discount: number;
} | null;
};
type CartAction =
| { type: 'ADD_ITEM'; product: CartItem }
| { type: 'REMOVE_ITEM'; id: number }
| { type: 'UPDATE_QUANTITY'; id: number; quantity: number }
| { type: 'APPLY_COUPON'; coupon: CartState['coupon'] }
| { type: 'REMOVE_COUPON' }
| { type: 'CLEAR_CART' };
const initialState: CartState = {
items: [],
coupon: null
};
function cartReducer(state: CartState, action: CartAction): CartState {
switch (action.type) {
case 'ADD_ITEM': {
const existing = state.items.find(item => item.id === action.product.id);
if (existing) {
return {
...state,
items: state.items.map(item =>
item.id === action.product.id
? { ...item, quantity: item.quantity + 1 }
: item
)
};
}
return {
...state,
items: [...state.items, { ...action.product, quantity: 1 }]
};
}
case 'REMOVE_ITEM':
return {
...state,
items: state.items.filter(item => item.id !== action.id)
};
case 'UPDATE_QUANTITY':
if (action.quantity <= 0) {
return {
...state,
items: state.items.filter(item => item.id !== action.id)
};
}
return {
...state,
items: state.items.map(item =>
item.id === action.id
? { ...item, quantity: action.quantity }
: item
)
};
case 'APPLY_COUPON':
return {
...state,
coupon: action.coupon
};
case 'REMOVE_COUPON':
return {
...state,
coupon: null
};
case 'CLEAR_CART':
return initialState;
default:
return state;
}
}
function TypedShoppingCart() {
const [state, dispatch] = useReducer(cartReducer, initialState);
const handleAddItem = (product: CartItem) => {
dispatch({ type: 'ADD_ITEM', product });
};
return (
<div>
{/* UI 组件 */}
</div>
);
}6.2 复杂状态类型
typescript
type AsyncState<T> = {
data: T | null;
loading: boolean;
error: string | null;
};
type User = {
id: number;
name: string;
email: string;
};
type AppState = {
user: AsyncState<User>;
theme: 'light' | 'dark';
notifications: Array<{
id: number;
type: 'info' | 'success' | 'warning' | 'error';
message: string;
}>;
};
type AppAction =
| { type: 'FETCH_USER_START' }
| { type: 'FETCH_USER_SUCCESS'; user: User }
| { type: 'FETCH_USER_ERROR'; error: string }
| { type: 'SET_THEME'; theme: AppState['theme'] }
| { type: 'ADD_NOTIFICATION'; notification: Omit<AppState['notifications'][0], 'id'> }
| { type: 'REMOVE_NOTIFICATION'; id: number };
const initialAppState: AppState = {
user: {
data: null,
loading: false,
error: null
},
theme: 'light',
notifications: []
};
function appReducer(state: AppState, action: AppAction): AppState {
switch (action.type) {
case 'FETCH_USER_START':
return {
...state,
user: {
...state.user,
loading: true,
error: null
}
};
case 'FETCH_USER_SUCCESS':
return {
...state,
user: {
data: action.user,
loading: false,
error: null
}
};
case 'FETCH_USER_ERROR':
return {
...state,
user: {
...state.user,
loading: false,
error: action.error
}
};
case 'SET_THEME':
return {
...state,
theme: action.theme
};
case 'ADD_NOTIFICATION':
return {
...state,
notifications: [
...state.notifications,
{ id: Date.now(), ...action.notification }
]
};
case 'REMOVE_NOTIFICATION':
return {
...state,
notifications: state.notifications.filter(n => n.id !== action.id)
};
default:
return state;
}
}第七部分:性能优化
7.1 避免不必要的重渲染
jsx
const AppContext = createContext(null);
function OptimizedAppProvider({ children }) {
const [state, dispatch] = useReducer(appReducer, initialState);
// 使用useMemo避免每次渲染创建新对象
const value = useMemo(() => ({ state, dispatch }), [state]);
return (
<AppContext.Provider value={value}>
{children}
</AppContext.Provider>
);
}
// 使用选择器获取特定状态
function useAppSelector(selector) {
const { state } = useContext(AppContext);
return selector(state);
}
// 只订阅需要的状态
function UserDisplay() {
// 只会在user变化时重渲染
const user = useAppSelector(state => state.user);
return <div>User: {user?.name}</div>;
}
// 使用React.memo避免不必要的重渲染
const MemoizedComponent = React.memo(function Component({ item, onUpdate }) {
return (
<div>
<h3>{item.name}</h3>
<button onClick={onUpdate}>更新</button>
</div>
);
});
function ParentComponent() {
const [state, dispatch] = useReducer(reducer, initialState);
// 使用useCallback缓存回调函数
const handleUpdate = useCallback((id) => {
dispatch({ type: 'UPDATE_ITEM', id });
}, []);
return (
<div>
{state.items.map(item => (
<MemoizedComponent
key={item.id}
item={item}
onUpdate={() => handleUpdate(item.id)}
/>
))}
</div>
);
}7.2 拆分大型Reducer
jsx
// 将大型reducer拆分为多个小型reducer
function combineReducers(reducers) {
return (state = {}, action) => {
const newState = {};
for (const key in reducers) {
newState[key] = reducers[key](state[key], action);
}
return newState;
};
}
function userReducer(state = { data: null }, action) {
switch (action.type) {
case 'LOGIN':
return { data: action.user };
case 'LOGOUT':
return { data: null };
default:
return state;
}
}
function settingsReducer(state = { theme: 'light' }, action) {
switch (action.type) {
case 'SET_THEME':
return { ...state, theme: action.theme };
default:
return state;
}
}
const rootReducer = combineReducers({
user: userReducer,
settings: settingsReducer
});
function App() {
const [state, dispatch] = useReducer(rootReducer, {});
return (
<div>
{/* 组件 */}
</div>
);
}第八部分:React 19新特性
8.1 使用Server Actions
jsx
'use server';
async function updateCartAction(formData) {
const productId = formData.get('productId');
const quantity = parseInt(formData.get('quantity'));
// 服务器端逻辑
await updateDatabase(productId, quantity);
return { success: true };
}
'use client';
function ShoppingCartWithServerActions() {
const [state, dispatch] = useReducer(cartReducer, initialState);
const [isPending, startTransition] = useTransition();
const handleUpdateQuantity = async (id, quantity) => {
startTransition(async () => {
const formData = new FormData();
formData.set('productId', id);
formData.set('quantity', quantity);
const result = await updateCartAction(formData);
if (result.success) {
dispatch({ type: 'UPDATE_QUANTITY', id, quantity });
}
});
};
return (
<div>
{state.items.map(item => (
<div key={item.id}>
<h3>{item.name}</h3>
<input
type="number"
value={item.quantity}
onChange={(e) => handleUpdateQuantity(item.id, parseInt(e.target.value))}
disabled={isPending}
/>
</div>
))}
{isPending && <div>更新中...</div>}
</div>
);
}8.2 使用useActionState
jsx
import { useActionState } from 'react';
async function submitFormAction(prevState, formData) {
const data = {
name: formData.get('name'),
email: formData.get('email')
};
try {
await submitToServer(data);
return { success: true, error: null };
} catch (error) {
return { success: false, error: error.message };
}
}
function FormWithActionState() {
const [formState, submitAction, isPending] = useActionState(
submitFormAction,
{ success: false, error: null }
);
return (
<form action={submitAction}>
<input name="name" placeholder="姓名" required />
<input name="email" type="email" placeholder="邮箱" required />
<button type="submit" disabled={isPending}>
{isPending ? '提交中...' : '提交'}
</button>
{formState.success && <p className="success">提交成功!</p>}
{formState.error && <p className="error">{formState.error}</p>}
</form>
);
}注意事项
1. Reducer纯函数原则
reducer必须是纯函数,不能包含:
jsx
// 错误示例
function badReducer(state, action) {
switch (action.type) {
case 'ADD_ITEM':
// 直接修改state
state.items.push(action.item); // ❌
return state;
case 'FETCH_DATA':
// 在reducer中进行异步操作
fetch('/api/data').then(/* ... */); // ❌
return state;
case 'UPDATE_TIME':
// 使用非确定性函数
return { ...state, time: Date.now() }; // ❌
default:
return state;
}
}
// 正确示例
function goodReducer(state, action) {
switch (action.type) {
case 'ADD_ITEM':
// 返回新对象
return {
...state,
items: [...state.items, action.item]
};
case 'SET_DATA':
// 只处理同步逻辑
return {
...state,
data: action.payload
};
case 'SET_TIME':
// 时间从action中获取
return {
...state,
time: action.timestamp
};
default:
return state;
}
}2. 状态不可变性
始终返回新对象,不要修改原有state:
jsx
// 错误
case 'UPDATE_USER':
state.user.name = action.name; // ❌
return state;
// 正确
case 'UPDATE_USER':
return {
...state,
user: {
...state.user,
name: action.name
}
};3. Action类型命名
使用统一的命名规范:
jsx
// 推荐:动词_名词格式
const ActionTypes = {
ADD_ITEM: 'ADD_ITEM',
REMOVE_ITEM: 'REMOVE_ITEM',
UPDATE_QUANTITY: 'UPDATE_QUANTITY',
FETCH_START: 'FETCH_START',
FETCH_SUCCESS: 'FETCH_SUCCESS',
FETCH_ERROR: 'FETCH_ERROR'
};4. 避免过度使用
不是所有状态都需要useReducer:
jsx
// 简单状态用useState
const [count, setCount] = useState(0);
// 复杂状态用useReducer
const [complexState, dispatch] = useReducer(complexReducer, initialState);5. 性能考虑
大型应用中需要注意性能:
jsx
// 使用useMemo缓存计算结果
const total = useMemo(() => {
return state.items.reduce((sum, item) => sum + item.price * item.quantity, 0);
}, [state.items]);
// 使用useCallback缓存dispatch函数
const handleAdd = useCallback((item) => {
dispatch({ type: 'ADD_ITEM', item });
}, []);
// 使用React.memo避免不必要的重渲染
const MemoizedItem = React.memo(CartItem);常见问题
1. 为什么dispatch后状态没有立即更新?
dispatch和setState一样是异步的:
jsx
function Component() {
const [state, dispatch] = useReducer(reducer, initialState);
const handleClick = () => {
dispatch({ type: 'INCREMENT' });
console.log(state.count); // ❌ 仍然是旧值
// 在下次渲染时才能获取新值
};
useEffect(() => {
console.log(state.count); // ✅ 新值
}, [state.count]);
return <button onClick={handleClick}>+1</button>;
}2. 如何在reducer中处理异步操作?
异步操作应该在组件中处理,不在reducer中:
jsx
function Component() {
const [state, dispatch] = useReducer(reducer, initialState);
const fetchData = async () => {
dispatch({ type: 'FETCH_START' });
try {
const response = await fetch('/api/data');
const data = await response.json();
dispatch({ type: 'FETCH_SUCCESS', payload: data });
} catch (error) {
dispatch({ type: 'FETCH_ERROR', error: error.message });
}
};
useEffect(() => {
fetchData();
}, []);
return <div>{/* UI */}</div>;
}3. 如何在多个组件间共享useReducer状态?
使用Context:
jsx
const StateContext = createContext(null);
function Provider({ children }) {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<StateContext.Provider value={{ state, dispatch }}>
{children}
</StateContext.Provider>
);
}
function useSharedState() {
return useContext(StateContext);
}4. 如何调试复杂的reducer?
使用reducer中间件:
jsx
function reducerWithLogger(reducer) {
return (state, action) => {
console.group(action.type);
console.log('Previous State:', state);
console.log('Action:', action);
const nextState = reducer(state, action);
console.log('Next State:', nextState);
console.groupEnd();
return nextState;
};
}
const [state, dispatch] = useReducer(
reducerWithLogger(myReducer),
initialState
);5. reducer太大怎么办?
拆分成多个小reducer:
jsx
function userReducer(state, action) {
// 处理用户相关action
}
function cartReducer(state, action) {
// 处理购物车相关action
}
function rootReducer(state, action) {
return {
user: userReducer(state.user, action),
cart: cartReducer(state.cart, action)
};
}6. 如何实现撤销/重做功能?
保存历史状态:
jsx
function historyReducer(reducer) {
const initialHistoryState = {
past: [],
present: null,
future: []
};
return (state = initialHistoryState, action) => {
const { past, present, future } = state;
switch (action.type) {
case 'UNDO':
if (past.length === 0) return state;
return {
past: past.slice(0, past.length - 1),
present: past[past.length - 1],
future: [present, ...future]
};
case 'REDO':
if (future.length === 0) return state;
return {
past: [...past, present],
present: future[0],
future: future.slice(1)
};
default:
const newPresent = reducer(present, action);
if (present === newPresent) return state;
return {
past: [...past, present],
present: newPresent,
future: []
};
}
};
}总结
useReducer实战要点
适用场景
- 复杂的状态逻辑
- 多个相关状态
- 状态更新依赖当前状态
- 需要在多个组件间共享状态
最佳实践
- reducer必须是纯函数
- 保持状态不可变性
- 使用统一的action命名
- 合理拆分reducer
- 结合TypeScript使用
性能优化
- 使用useMemo缓存计算
- 使用useCallback缓存回调
- 使用React.memo避免重渲染
- 拆分Context避免过度渲染
常见模式
- 购物车系统
- 表单管理
- 数据获取
- 游戏状态
- 全局状态管理
与其他方案对比
- useState: 简单状态
- useReducer: 复杂状态
- Redux: 大型应用全局状态
- Context + useReducer: 中型应用全局状态
React 19增强
- Server Actions集成
- useActionState简化表单
- 更好的异步支持
- 自动批处理优化
通过本章的实战案例,你已经掌握了useReducer在各种真实场景中的应用。记住,选择合适的工具很重要,不要过度使用useReducer,但当需要管理复杂状态时,它是最好的选择!