"老师,我们的后台管理系统状态管理好混乱啊!"上周二的代码评审会上,小王一脸苦恼地说道。我打开代码仓库看了看,确实问题不小 - 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>
)
}
这种方式存在几个明显的问题:
- 服务器状态和客户端状态混在一起
- 大量重复的样板代码
- 缓存和数据同步困难
- 性能优化不好做
重构方案
经过团队讨论,我们决定采用"分而治之"的策略:
- 使用 React Query 管理 服务器状态
- 使用 Zustand 管理本地 UI 状态
- 使用 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>
)
}
重构过程
为了平滑过渡,我们采用了渐进式重构策略:
- 首先创建一个自定义 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>
)
}
- 然后逐步迁移状态管理:
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>
}
效果验证
重构后,我们观察到了明显的改善:
- 代码更清晰,职责划分明确
- 缓存管理更智能,性能提升明显
- 开发效率提高,不用写那么多样板 代 码
- 数据同步问题大大减少
最让我印象深刻的是小王的反馈:"现在代码写起来舒服多了,不用担心状态同步的问题!"
经验总结
这次重构让我们学到了很多:
- 不同类型的状态要用不同的工具管理
- 缓存策略要根据业务场景来设计
- 渐进式重构比大规模重写更可控
- 好的抽象能大大提高开发效率
就像整理房间一样,不同类型的物品要放在不同的地方。把衣服、书籍、电子产品分类存放,不仅容易找,也更好维护。状态管理也是一样,合适的工具管理合适的状态,才能让代码更清晰、更好维护。
写在最后
状态管理没有银弹,关键是要根据实际需求选择合适的方案。就像选择家具一样,不是越贵越好,而是要适合自己的需求。
有什么问题欢迎在评论区讨论,让我们一起探讨状态管理的最佳实践!
如果觉得有帮助,别忘了点赞关注,我会继续分享更多实战经验~