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

"老师,我们的后台管理系统状态管理好混乱啊!"上周二的代码评审会上,小王一脸苦恼地说道。我打开代码仓库看了看,确实问题不小 - Redux store 里堆满了各种数据,有本地状态,有服务器数据,还有一些缓存,导致代码难以维护,性能也受到影响。

说实话,这个问题在中后台项目中很常见。随着项目的发展,状态管理往往会变得越来越复杂。今天就来分享一下我们是如何通过重构解决这个问题的。

问题的症状

首先,让我们看看重构前的代码是什么样子:

typescript 复制代码
// 原来的 Redux store
interface AppState {
  // 本地 UI 状态
  ui: {
    theme: string
    sidebar: boolean
    modal: {
      visible: boolean
      type: string
    }
  }
  // 服务器数据
  users: {
    list: User[]
    loading: boolean
    error: Error | null
    lastUpdated: number
  }
  products: {
    list: Product[]
    loading: boolean
    error: Error | null
    lastUpdated: number
  }
  // 表单状态
  forms: {
    userForm: {
      values: any
      errors: any
      touched: boolean[]
    }
    productForm: {
      values: any
      errors: any
      touched: 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 管理表单状态
typescript 复制代码
// 使用 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: string
  sidebar: boolean
  setTheme: (theme: string) => void
  toggleSidebar: () => 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 封装数据获取逻辑:
typescript 复制代码
// 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. 然后逐步迁移状态管理:
typescript 复制代码
// 新的状态管理结构
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. 好的抽象能大大提高开发效率

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

写在最后

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

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

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

相关推荐
m0_748229993 分钟前
前端WebSocket应用——聊天实时通信的基本配置
前端·websocket·网络协议
爱lv行6 分钟前
使用 rbenv 切换 Ruby 版本
开发语言·前端·ruby
贼爱学习的小黄7 分钟前
Jenkins自动化部署前端项目
前端·自动化·jenkins
kida_yuan21 分钟前
【从零开始】9. RAG 应用调优-再续(番外篇)
人工智能·pytorch·监控
蜂鸟视图fengmap21 分钟前
玩转个性地图样式!蜂鸟视图蜂鸟云主题编辑器正式上线
前端·主题编辑器·蜂鸟视图·地图设计·智能地图·地图样式定制·三维地图引擎
合合技术团队26 分钟前
DocFlow票据AI自动化处理工具:出色的文档解析+抽取能力,提升企业文档数字化管理效能
大数据·人工智能·自动化·文档解析
儒道易行27 分钟前
【DVWA】XSS(Stored)
前端·网络安全·xss
Amo 672928 分钟前
JavaScript 中的 new 和构造函数:传统方式与 ES6 语法糖对比
前端·javascript·es6
&永恒的星河&40 分钟前
深度学习NLP篇—基于词图的机械分词
人工智能·分词算法·基于词图的分词算法·机械分词·nlp中文分词算法
MongoDB 数据平台41 分钟前
MongoDB与阿里云庆祝合作五周年,展望AI赋能新未来
人工智能·mongodb·阿里云