"又是状态管理的问题!"这是我们团队在重构一个大型电商后台时经常听到的抱怨。随着业务的快速发展,原本清晰的 Redux 架构逐渐变得臃肿不堪。本文将分享我们如何通过技术演进,最终构建出一个优雅高效的状态管理方案。🛠️
问题的源头
让我们先看看原始代码中的问题:
typescript
// 典型的 Redux 代码
// actions/product.ts
export const fetchProducts = () => async (dispatch) => {
dispatch({ type: 'FETCH_PRODUCTS_START' });
try {
const response = await api.getProducts();
dispatch({ type: 'FETCH_PRODUCTS_SUCCESS', payload: response.data });
} catch (error) {
dispatch({ type: 'FETCH_PRODUCTS_ERROR', payload: error });
}
};
// reducers/product.ts
const productReducer = (state = initialState, action) => {
switch (action.type) {
case 'FETCH_PRODUCTS_START':
return { ...state, loading: true };
case 'FETCH_PRODUCTS_SUCCESS':
return {
...state,
loading: false,
data: action.payload
};
case 'FETCH_PRODUCTS_ERROR':
return {
...state,
loading: false,
error: action.payload
};
default:
return state;
}
};
主要问题:
- 大量的样板代码
- 异步操作处理复杂
- 数据缓存和更新困难
- 代码复用性差
演进过程
1. 优化 Redux 架构
首先,我们尝试通过自定义中间件和工具函数优化 Redux:
typescript
// utils/createAsyncAction.ts
interface AsyncAction<T> {
type: string;
payload?: T;
error?: Error;
meta?: any;
}
function createAsyncAction<T>(type: string) {
return {
request: (): AsyncAction<void> => ({
type: `${type}_REQUEST`
}),
success: (payload: T): AsyncAction<T> => ({
type: `${type}_SUCCESS`,
payload
}),
failure: (error: Error): AsyncAction<Error> => ({
type: `${type}_FAILURE`,
error
})
};
}
// 使用示例
const productActions = createAsyncAction<Product[]>('FETCH_PRODUCTS');
// 更简洁的异步操作
async function fetchProducts(dispatch) {
try {
dispatch(productActions.request());
const data = await api.getProducts();
dispatch(productActions.success(data));
} catch (error) {
dispatch(productActions.failure(error));
}
}
2. 引入 Redux Toolkit
接下来,我们使用 Redux Toolkit 简化状态管理:
typescript
// features/products/productsSlice.ts
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
export const fetchProducts = createAsyncThunk(
'products/fetch',
async (_, { rejectWithValue }) => {
try {
const response = await api.getProducts();
return response.data;
} catch (error) {
return rejectWithValue(error);
}
}
);
const productsSlice = createSlice({
name: 'products',
initialState: {
data: [],
loading: false,
error: null
},
reducers: {
updateProduct: (state, action) => {
const index = state.data.findIndex(p => p.id === action.payload.id);
if (index !== -1) {
state.data[index] = action.payload;
}
}
},
extraReducers: (builder) => {
builder
.addCase(fetchProducts.pending, (state) => {
state.loading = true;
})
.addCase(fetchProducts.fulfilled, (state, action) => {
state.loading = false;
state.data = action.payload;
})
.addCase(fetchProducts.rejected, (state, action) => {
state.loading = false;
state.error = action.payload;
});
}
});
3. 引入 React Query
最后,我们发现很多状态其实是服务器数据的缓存,于是引入了 React Query:
typescript
// hooks/useProducts.ts
import { useQuery, useMutation, useQueryClient } from 'react-query';
export function useProducts() {
return useQuery('products', api.getProducts, {
staleTime: 5 * 60 * 1000, // 5分钟内认为数据是新鲜的
cacheTime: 30 * 60 * 1000, // 缓存30分钟
});
}
export function useUpdateProduct() {
const queryClient = useQueryClient();
return useMutation(api.updateProduct, {
// 乐观更新
onMutate: async (newProduct) => {
await queryClient.cancelQueries('products');
const previousProducts = queryClient.getQueryData('products');
queryClient.setQueryData('products', (old: Product[]) => {
return old.map(p =>
p.id === newProduct.id ? newProduct : p
);
});
return { previousProducts };
},
// 错误回滚
onError: (err, newProduct, context) => {
queryClient.setQueryData('products', context.previousProducts);
},
// 成功后使缓存失效
onSettled: () => {
queryClient.invalidateQueries('products');
}
});
}
4. 状态分层管理
我们将状态按照特性进行分层:
typescript
// 1. 服务器状态(使用 React Query)
function ProductList() {
const { data: products, isLoading } = useProducts();
const updateProduct = useUpdateProduct();
if (isLoading) return <Spinner />;
return (
<div>
{products.map(product => (
<ProductCard
key={product.id}
product={product}
onUpdate={updateProduct.mutate}
/>
))}
</div>
);
}
// 2. UI 状态(使用 useState)
function ProductCard({ product, onUpdate }) {
const [isEditing, setIsEditing] = useState(false);
return (
<Card>
{isEditing ? (
<ProductForm
product={product}
onSubmit={(data) => {
onUpdate(data);
setIsEditing(false);
}}
/>
) : (
<ProductDisplay
product={product}
onEdit={() => setIsEditing(true)}
/>
)}
</Card>
);
}
// 3. 全局 UI 状态(使用 Context)
const ThemeContext = createContext<Theme>('light');
function useTheme() {
const theme = useContext(ThemeContext);
if (!theme) {
throw new Error('useTheme must be used within ThemeProvider');
}
return theme;
}
// 4. 表单状态(使用 React Hook Form)
function ProductForm({ product, onSubmit }) {
const { register, handleSubmit } = useForm({
defaultValues: product
});
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register('name')} />
<input {...register('price')} />
<button type="submit">保存</button>
</form>
);
}
5. 性能优化
实现了智能的缓存策略:
typescript
// hooks/useOptimisticUpdate.ts
function useOptimisticUpdate<T>(
queryKey: string,
mutationFn: (data: T) => Promise<T>
) {
const queryClient = useQueryClient();
return useMutation(mutationFn, {
onMutate: async (newData) => {
await queryClient.cancelQueries(queryKey);
const snapshot = queryClient.getQueryData(queryKey);
queryClient.setQueryData(queryKey, (old: T[]) => {
return Array.isArray(old)
? [...old, newData]
: [newData];
});
return { snapshot };
},
onError: (_, __, context) => {
queryClient.setQueryData(queryKey, context.snapshot);
},
onSettled: () => {
queryClient.invalidateQueries(queryKey);
}
});
}
// 使用示例
function ProductList() {
const optimisticUpdate = useOptimisticUpdate('products', api.createProduct);
const handleAdd = async (product) => {
try {
await optimisticUpdate.mutateAsync(product);
toast.success('产品添加成功!');
} catch (error) {
toast.error('添加失败,请重试');
}
};
return (/* ... */);
}
最终架构
我们的最终状态管理方案是这样的:
-
服务器状态:React Query
- 自动缓存和重新验证
- 乐观更新
- 后台重新获取
-
客户端状态:
- 局部 UI 状态:useState
- 全局 UI 状态:Context
- 表单状态:React Hook Form
-
性能优化:
- 查询缓存
- 乐观更新
- 后台同步
- 智能重新获取
收获与思考
经过这次重构,我们学到了很多:
- 不是所有状态都需要全局管理
- 选择合适的工具很重要
- 性能优化要从架构层面考虑
- 代码可维护性是首要目标
最让我欣慰的是团队成员的反馈:"现在的代码清晰多了,维护起来也轻松!"😊
写在最后
状态管理没有银弹,关键是要:
- 理解不同类型的状态
- 选择合适的管理方案
- 持续优化和改进
- 保持代码的可维护性
有什么问题欢迎在评论区讨论,我们一起学习进步!
如果觉得有帮助,别忘了点赞关注,我会继续分享更多实战经验~