"老师,我们的后台管理系统状态管理好混乱啊!"上周二的代码评审会上,小王一脸苦恼地说道。我打开代码仓库看了看,确实问题不小 - Redux store 里堆满了各种数据,有本地状态,有服务器数据,还有一些缓存,导致代码难以维护,性能也受到影响。
说实话,这个问题在中后台项目中很常见。随着项目的发展,状态管理往往会变得越来越复杂。今天就来分享一下我们是如何通过重构解决这个问题的。
问题的症状
首先,让我们看看重构前的代码是什么样子:
// 原来的 Redux store
interface AppState {// 本地 UI 状态ui: {theme: stringsidebar: booleanmodal: {visible: booleantype: string}}// 服务器数据users: {list: User[]loading: booleanerror: Error | nulllastUpdated: number}products: {list: Product[]loading: booleanerror: Error | nulllastUpdated: number}// 表单状态forms: {userForm: {values: anyerrors: anytouched: boolean[]}productForm: {values: anyerrors: anytouched: boolean[]}}
}// 获取用户数据的 action
const fetchUsers = () => async dispatch => {dispatch({ type: 'FETCH_USERS_START' })try {const response = await api.get('/users')dispatch({type: 'FETCH_USERS_SUCCESS',payload: response.data,lastUpdated: Date.now()})} catch (error) {dispatch({ type: 'FETCH_USERS_ERROR', error })}
}// 组件中的使用
function UserList() {const dispatch = useDispatch()const { list: users, loading, error } = useSelector(state => state.users)useEffect(() => {dispatch(fetchUsers())}, [dispatch])if (loading) return <Loading />if (error) return <Error message={error.message} />return (<div>{users.map(user => (<UserCard key={user.id} user={user} />))}</div>)
}
这种方式存在几个明显的问题:
- 服务器状态和客户端状态混在一起
- 大量重复的样板代码
- 缓存和数据同步困难
- 性能优化不好做
重构方案
经过团队讨论,我们决定采用"分而治之"的策略:
- 使用 React Query 管理 服务器状态
- 使用 Zustand 管理本地 UI 状态
- 使用 React Hook Form 管理表单状态
// 使用 React Query 管理服务器状态
function useUsers() {return useQuery({queryKey: ['users'],queryFn: () => api.get('/users').then(res => res.data),staleTime: 5 * 60 * 1000, // 5分钟内认为数据是新鲜的cacheTime: 30 * 60 * 1000 // 缓存30分钟})
}// 使用 Zustand 管理 UI 状态
interface UIStore {theme: stringsidebar: booleansetTheme: (theme: string) => voidtoggleSidebar: () => void
}const useUIStore = create<UIStore>(set => ({theme: 'light',sidebar: true,setTheme: theme => set({ theme }),toggleSidebar: () => set(state => ({ sidebar: !state.sidebar }))
}))// 使用 React Hook Form 管理表单
function UserForm() {const {register,handleSubmit,formState: { errors }} = useForm<UserFormData>()const queryClient = useQueryClient()const mutation = useMutation({mutationFn: (data: UserFormData) => api.post('/users', data),onSuccess: () => {// 成功后使缓存失效queryClient.invalidateQueries({ queryKey: ['users'] })}})const onSubmit = handleSubmit(data => {mutation.mutate(data)})return (<form onSubmit={onSubmit}><input {...register('name', { required: true })} />{errors.name && <span>名字是必填的</span>}<button type='submit'>提交</button></form>)
}
重构过程
为了平滑过渡,我们采用了渐进式重构策略:
- 首先创建一个自定义 Hook 封装数据获取逻辑:
// hooks/useResource.ts
function useResource<T>(resource: string) {const query = useQuery({queryKey: [resource],queryFn: () => api.get(`/${resource}`).then(res => res.data),// 配置缓存策略staleTime: 5 * 60 * 1000,cacheTime: 30 * 60 * 1000,// 乐观更新配置optimisticResults: true,// 重试策略retry: 3,retryDelay: attemptIndex => Math.min(1000 * 2 ** attemptIndex, 30000)})const mutation = useMutation({mutationFn: (data: Partial<T>) => api.post(`/${resource}`, data),onSuccess: () => {// 更新缓存query.invalidate()}})return {data: query.data,isLoading: query.isLoading,error: query.error,create: mutation.mutate,isCreating: mutation.isLoading}
}// 使用示例
function UserList() {const { data: users, isLoading, error } = useResource<User>('users')if (isLoading) return <Loading />if (error) return <Error message={error.message} />return (<div>{users.map(user => (<UserCard key={user.id} user={user} />))}</div>)
}
- 然后逐步迁移状态管理:
// 新的状态管理结构
interface AppState {// Zustand 管理 UI 状态ui: UIStore// React Query 管理服务器状态// - 用户数据// - 产品数据// - 订单数据// React Hook Form 管理表单状态// - 用户表单// - 产品表单
}// 性能优化
function UserList() {const { data: users, isLoading } = useResource<User>('users')// 使用 React Query 的内置缓存const { data: roles } = useQuery({queryKey: ['roles'],queryFn: () => api.get('/roles').then(res => res.data),// 只有当有用户数据时才获取角色enabled: !!users})// 使用 memo 优化渲染const userCards = useMemo(() => users?.map(user => <UserCard key={user.id} user={user} role={roles?.find(role => role.id === user.roleId)} />), [users, roles])if (isLoading) return <Loading />return <div>{userCards}</div>
}
效果验证
重构后,我们观察到了明显的改善:
- 代码更清晰,职责划分明确
- 缓存管理更智能,性能提升明显
- 开发效率提高,不用写那么多样板 代 码
- 数据同步问题大大减少
最让我印象深刻的是小王的反馈:"现在代码写起来舒服多了,不用担心状态同步的问题!"
经验总结
这次重构让我们学到了很多:
- 不同类型的状态要用不同的工具管理
- 缓存策略要根据业务场景来设计
- 渐进式重构比大规模重写更可控
- 好的抽象能大大提高开发效率
就像整理房间一样,不同类型的物品要放在不同的地方。把衣服、书籍、电子产品分类存放,不仅容易找,也更好维护。状态管理也是一样,合适的工具管理合适的状态,才能让代码更清晰、更好维护。
写在最后
状态管理没有银弹,关键是要根据实际需求选择合适的方案。就像选择家具一样,不是越贵越好,而是要适合自己的需求。
有什么问题欢迎在评论区讨论,让我们一起探讨状态管理的最佳实践!
如果觉得有帮助,别忘了点赞关注,我会继续分享更多实战经验~