跳到主要内容

React 状态管理深度对比

状态管理方案全面对比

1. Redux Toolkit(RTK)

// slice 定义
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'

export const fetchUser = createAsyncThunk(
'user/fetchById',
async (userId: string, { rejectWithValue }) => {
try {
return await api.getUser(userId)
} catch (err) {
return rejectWithValue(err.message)
}
}
)

const userSlice = createSlice({
name: 'user',
initialState: { data: null, loading: false, error: null } as UserState,
reducers: {
clearUser: state => { state.data = null } // Immer 支持「可变」写法
},
extraReducers: builder => {
builder
.addCase(fetchUser.pending, state => { state.loading = true })
.addCase(fetchUser.fulfilled, (state, action) => {
state.loading = false
state.data = action.payload
})
.addCase(fetchUser.rejected, (state, action) => {
state.loading = false
state.error = action.payload as string
})
}
})

RTK Query(数据获取层)

const api = createApi({
reducerPath: 'api',
baseQuery: fetchBaseQuery({ baseUrl: '/api' }),
tagTypes: ['User', 'Post'],
endpoints: builder => ({
getUser: builder.query<User, string>({
query: id => `/users/${id}`,
providesTags: (result, err, id) => [{ type: 'User', id }]
}),
updateUser: builder.mutation<User, Partial<User>>({
query: ({ id, ...body }) => ({ url: `/users/${id}`, method: 'PUT', body }),
invalidatesTags: (result, err, { id }) => [{ type: 'User', id }]
})
})
})

// 组件中使用
const { data: user, isLoading } = useGetUserQuery(userId)
const [updateUser, { isLoading: isUpdating }] = useUpdateUserMutation()

2. Zustand

// 轻量,基于 hooks,无需 Provider
import { create } from 'zustand'
import { devtools, persist, immer } from 'zustand/middleware'

const useUserStore = create<UserStore>()(
devtools(
persist(
immer(set => ({
user: null,
loading: false,
fetchUser: async (id: string) => {
set(state => { state.loading = true })
const user = await api.getUser(id)
set(state => { state.user = user; state.loading = false })
},
clearUser: () => set({ user: null })
})),
{ name: 'user-storage' } // 自动持久化到 localStorage
),
{ name: 'UserStore' } // DevTools 中显示的名称
)
)

// 精确订阅(避免不必要的重渲染)
const user = useUserStore(state => state.user)
const loading = useUserStore(state => state.loading)
// 不要:const { user, loading } = useUserStore() 会订阅整个 store

Zustand 的切片模式(大型应用)

// 将 store 拆成多个切片组合
const useUserSlice = (set, get) => ({
user: null,
setUser: (user) => set({ user })
})

const useCartSlice = (set, get) => ({
items: [],
addItem: (item) => set(state => ({ items: [...state.items, item] }))
})

const useBoundStore = create((...a) => ({
...useUserSlice(...a),
...useCartSlice(...a)
}))

3. Jotai(原子化状态)

import { atom, useAtom, useAtomValue, useSetAtom } from 'jotai'
import { atomWithQuery } from 'jotai-tanstack-query'

// 基础 atom
const countAtom = atom(0)
const doubleAtom = atom(get => get(countAtom) * 2) // 派生 atom

// 异步 atom
const userAtom = atomWithQuery(get => ({
queryKey: ['user', get(userIdAtom)],
queryFn: () => fetchUser(get(userIdAtom))
}))

// 只订阅需要的 atom,精确更新
function Counter() {
const [count, setCount] = useAtom(countAtom)
const double = useAtomValue(doubleAtom) // 只读
// ...
}

4. 方案选型指南

场景推荐方案
大型企业应用,需要 DevTools 调试Redux Toolkit
中型应用,追求简洁Zustand
细粒度更新,服务端状态Jotai + React Query
纯服务端状态管理React Query / SWR
简单全局状态Context + useReducer

5. Context 性能优化

// 问题:Context 值变化导致所有消费者重渲染
const ThemeContext = createContext(null)

// 方案1:拆分 Context(读写分离)
const ThemeValueContext = createContext(null)
const ThemeDispatchContext = createContext(null)

function ThemeProvider({ children }) {
const [theme, dispatch] = useReducer(reducer, initialState)
return (
<ThemeValueContext.Provider value={theme}>
<ThemeDispatchContext.Provider value={dispatch}>
{children}
</ThemeDispatchContext.Provider>
</ThemeValueContext.Provider>
)
}

// 方案2:useMemo 稳定 value 引用
function Provider({ children }) {
const [user, setUser] = useState(null)
const value = useMemo(() => ({ user, setUser }), [user])
return <Ctx.Provider value={value}>{children}</Ctx.Provider>
}

// 方案3:使用 use-context-selector 精确订阅
import { createContext, useContextSelector } from 'use-context-selector'
const user = useContextSelector(AppContext, ctx => ctx.user)

6. 资深面试题

  • Redux 与 Zustand 的根本区别?
    • Redux:单一 store,严格的单向数据流,action -> reducer -> state
    • Zustand:多 store,直接更新 state,无 action 约束,更灵活
    • Redux 更适合需要 time-travel 调试、复杂中间件的场景
  • 为什么说 React Query 不是状态管理工具?
    • React Query 管理的是服务端状态的缓存和同步
    • 状态管理工具管理的是纯客户端状态
    • 两者职责不同,可以共存使用
  • Jotai 如何解决 Context 的性能问题?
    • 每个 atom 是独立的 Context,消费者只订阅用到的 atom
    • 使用 WeakMap 存储 atom 值,不同组件订阅不同 atom 互不影响
    • 派生 atom 只在依赖变化时重新计算