状态管理实战:一次 Redux 到 React Query 的重构之旅

ops/2024/12/14 15:11:41/

"老师,我们的后台管理系统状态管理好混乱啊!"上周二的代码评审会上,小王一脸苦恼地说道。我打开代码仓库看了看,确实问题不小 - 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>)
}

这种方式存在几个明显的问题:

  1. 服务器状态和客户端状态混在一起
  2. 大量重复的样板代码
  3. 缓存和数据同步困难
  4. 性能优化不好做

重构方案

经过团队讨论,我们决定采用"分而治之"的策略:

  1. 使用 React Query 管理 服务器状态
  2. 使用 Zustand 管理本地 UI 状态
  3. 使用 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>)
}

重构过程

为了平滑过渡,我们采用了渐进式重构策略:

  1. 首先创建一个自定义 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>)
}
  1. 然后逐步迁移状态管理:
// 新的状态管理结构
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>
}

效果验证

重构后,我们观察到了明显的改善:

  1. 代码更清晰,职责划分明确
  2. 缓存管理更智能,性能提升明显
  3. 开发效率提高,不用写那么多样板 代 码
  4. 数据同步问题大大减少

最让我印象深刻的是小王的反馈:"现在代码写起来舒服多了,不用担心状态同步的问题!"

经验总结

这次重构让我们学到了很多:

  1. 不同类型的状态要用不同的工具管理
  2. 缓存策略要根据业务场景来设计
  3. 渐进式重构比大规模重写更可控
  4. 好的抽象能大大提高开发效率

就像整理房间一样,不同类型的物品要放在不同的地方。把衣服、书籍、电子产品分类存放,不仅容易找,也更好维护。状态管理也是一样,合适的工具管理合适的状态,才能让代码更清晰、更好维护。

写在最后

状态管理没有银弹,关键是要根据实际需求选择合适的方案。就像选择家具一样,不是越贵越好,而是要适合自己的需求。

有什么问题欢迎在评论区讨论,让我们一起探讨状态管理的最佳实践!

如果觉得有帮助,别忘了点赞关注,我会继续分享更多实战经验~


http://www.ppmy.cn/ops/141843.html

相关文章

搭建一个基于Web的文档管理系统,用于存储、共享和协作编辑文档

搭建一个基于Web的文档管理系统&#xff0c;用于存储、共享和协作编辑文档 本项目采用以下架构&#xff1a; NFS****服务器: 负责存储文档资料。Web****服务器: 负责提供文档访问和编辑功能。SELinux: 负责权限控制&#xff0c;确保文档安全。Git服务器: 负责存储文档版本历史…

恢复删除的文件:6个免费Windows电脑数据恢复软件

数据恢复软件可帮助您从众多存储设备中恢复损坏或删除的数据。您可以使用这些文件恢复软件来检索文件、文档、视频、图片等。这些应用程序支持多种标准文件格式&#xff0c;如 PNG、RTF、PDF、HTML、JPG、MP3 等。 经过超过 75 小时的研究&#xff0c;我分析了 25 最佳免费数据…

linux网络编程 | c | epoll实现IO多路转接服务器

epoll实现IO多路转接服务器 可通过以下视频学习 06-opell函数实现的多路IO转接_哔哩哔哩_bilibili 通过响应式–多路IO转接实现 文章目录 epoll实现IO多路转接服务器1.思路&功能核心思路 2.代码实现multi_epoll_sever.c运行图 1.思路&功能 **功能&#xff1a;**客…

Vue是什么

Vue是什么 一种用于构建用户界面的渐进式JavaScript框架。它提供了一种简单灵活的方式来构建交互式的Web应用程序。Vue采用组件化的开发模式&#xff0c;通过将界面拆分为可重用的组件&#xff0c;使得开发者可以更加高效地进行开发和维护。Vue具有易学易用、灵活性强、性能优…

软考系分:今日成绩已出

前言 今年报考了11月份的软考高级&#xff1a;系统分析师。 考试时间&#xff1a;11月9日。 总体感觉偏简单&#xff0c;但是知识点记得不牢&#xff0c;估计机会不大。 今日 12.11 &#xff0c;成绩已出&#xff0c;每科总分 75分&#xff0c;全部45分以上为通过。 成绩总…

学习思考:一日三问(思考篇)之路由表

学习思考&#xff1a;一日三问&#xff08;思考篇&#xff09;之路由表 学了什么&#xff08;是什么&#xff09;Destination/Mask&#xff08;最终目标&#xff0c;寻路必须&#xff09;Proto&#xff08;择优可选&#xff09;Pre&#xff08;择优可选&#xff09;Cost&#x…

美化和定制你的Django Admin:使用SimpleUI

SimpleUI是一个简洁、美观的Django后台管理界面,它可以让你的Django Admin更加直观和易用。本文将指导你如何安装和配置SimpleUI,并进行自定义配置。 目录 安装Django创建Django项目创建Django app安装SimpleUI测试安装是否成功数据库迁移注册超级管理员登录验证自定义配置 …

android studio ladybug新建flutter项目步骤

新建完项目后需要做以下几步 1、gradle对应上 gradle-wrapper.properties中distributionUrl修改gradle版本号 Andorid/build.gradle中修改gradle版本号 2、如果用到了三方库需要在Andorid/build.gradle中增加 subprojects { afterEvaluate { project -> if…