React组件状态管理是前端工程化中的核心议题。React框架内置的useState和useContext在小型应用中足够使用,但当组件层级加深、共享状态增多时,手动传递props和Context嵌套会导致代码可维护性下降。Redux Toolkit和Zustand是当前社区主流的两个状态管理方案,两者在设计理念和API风格上有显著差异,选型需要根据项目规模和团队习惯决定。
Redux Toolkit:规范化状态管理方案
Redux Toolkit(RTK)是Redux官方推荐的编写方式,通过createSlice简化action/reducer模板代码,内置Immer实现不可变更新,集成Redux DevTools提供时间旅行调试能力。
// store.ts - 创建Redux Store
import { configureStore, createSlice, PayloadAction } from '@reduxjs/toolkit'
// 定义Slice
interface CounterState {
value: number
history: number[]
}
const counterSlice = createSlice({
name: 'counter',
initialState: { value: 0, history: [] } as CounterState,
reducers: {
increment: (state) => {
state.value += 1
state.history.push(state.value)
},
decrement: (state) => {
state.value -= 1
state.history.push(state.value)
},
incrementByAmount: (state, action: PayloadAction<number>) => {
state.value += action.payload
state.history.push(state.value)
},
reset: (state) => {
state.value = 0
state.history = []
},
},
})
export const { increment, decrement, incrementByAmount, reset } = counterSlice.actions
// 异步Action(createAsyncThunk)
import { createAsyncThunk } from '@reduxjs/toolkit'
export const fetchCounterValue = createAsyncThunk(
'counter/fetchValue',
async (userId: string) => {
const response = await fetch(`/api/counter/${userId}`)
return (await response.json()).value
}
)
const counterSliceWithAsync = createSlice({
name: 'counter',
initialState: {
value: 0,
history: [],
status: 'idle',
error: null as string | null,
},
reducers: {
increment: (state) => { state.value += 1 },
},
extraReducers: (builder) => {
builder
.addCase(fetchCounterValue.pending, (state) => {
state.status = 'loading'
})
.addCase(fetchCounterValue.fulfilled, (state, action) => {
state.status = 'succeeded'
state.value = action.payload
})
.addCase(fetchCounterValue.rejected, (state, action) => {
state.status = 'failed'
state.error = action.error.message || 'Unknown error'
})
},
})
// 配置Store
export const store = configureStore({
reducer: {
counter: counterSliceWithAsync.reducer,
},
})
// 类型导出
export type RootState = ReturnType<typeof store.getState>
export type AppDispatch = typeof store.dispatch
// hooks.ts - 类型安全的Dispatch和Selector Hooks
import { useDispatch, useSelector, TypedUseSelectorHook } from 'react-redux'
import type { RootState, AppDispatch } from './store'
export const useAppDispatch = () => useDispatch<AppDispatch>()
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector
// Counter.tsx - 组件使用
import { useAppDispatch, useAppSelector } from './hooks'
import { increment, decrement, fetchCounterValue } from './store'
function Counter() {
const dispatch = useAppDispatch()
const { value, status, error } = useAppSelector((state) => state.counter)
return (
<div>
<span>{value}</span>
<button onClick={() => dispatch(increment())}>+</button>
<button onClick={() => dispatch(decrement())}>-</button>
<button
onClick={() => dispatch(fetchCounterValue('user123'))}
disabled={status === 'loading'}
>
{status === 'loading' ? '加载中...' : '获取远程值'}
</button>
{error && <p>错误: {error}</p>}
</div>
)
}
Zustand:轻量级状态管理方案
Zustand采用更简洁的API设计,无需Provider包裹,直接通过create函数创建store hook。状态更新通过set函数完成,支持中间件扩展。
// store.ts - 创建Zustand Store
import { create } from 'zustand'
import { devtools, persist } from 'zustand/middleware'
interface CounterStore {
value: number
history: number[]
status: 'idle' | 'loading' | 'succeeded' | 'failed'
error: string | null
increment: () => void
decrement: () => void
incrementByAmount: (amount: number) => void
reset: () => void
fetchValue: (userId: string) => Promise<void>
}
export const useCounterStore = create<CounterStore>()(
devtools(
persist(
(set, get) => ({
value: 0,
history: [],
status: 'idle',
error: null,
increment: () =>
set((state) => ({
value: state.value + 1,
history: [...state.history, state.value + 1],
})),
decrement: () =>
set((state) => ({
value: state.value - 1,
history: [...state.history, state.value - 1],
})),
incrementByAmount: (amount) =>
set((state) => ({
value: state.value + amount,
history: [...state.history, state.value + amount],
})),
reset: () => set({ value: 0, history: [], status: 'idle', error: null }),
fetchValue: async (userId) => {
set({ status: 'loading', error: null })
try {
const response = await fetch(`/api/counter/${userId}`)
const data = await response.json()
set({ value: data.value, status: 'succeeded' })
} catch (err) {
set({ status: 'failed', error: (err as Error).message })
}
},
}),
{ name: 'counter-storage' }
)
)
)
// 组件使用
import { useCounterStore } from './store'
function Counter() {
const { value, status, error, increment, decrement, fetchValue } = useCounterStore()
return (
<div>
<span>{value}</span>
<button onClick={increment}>+</button>
<button onClick={decrement}>-</button>
<button
onClick={() => fetchValue('user123')}
disabled={status === 'loading'}
>
{status === 'loading' ? '加载中...' : '获取远程值'}
</button>
</div>
)
}
// 选择性订阅(避免不必要重渲染)
function CounterValue() {
// 只订阅value,其他状态变化不触发重渲染
const value = useCounterStore((state) => state.value)
return <span>{value}</span>
}
// 使用shallow进行多字段比较
import { shallow } from 'zustand/shallow'
function CounterActions() {
const { increment, decrement } = useCounterStore(
(state) => ({ increment: state.increment, decrement: state.decrement }),
shallow
)
return (
<>
<button onClick={increment}>+</button>
<button onClick={decrement}>-</button>
</>
)
}
两种方案的核心差异分析
模板代码量:RTK需要定义slice、configureStore、Provider、typed hooks,文件结构固定。Zustand仅需一个create函数,无Provider依赖。在10个以下store的中型项目中,Zustand代码量约为RTK的50%。
异步处理:RTK通过createAsyncThunk处理异步,自动生成pending/fulfilled/rejected三种状态action,配合extraReducers处理状态变更。Zustand在action函数内直接async/await,通过set更新status字段。RTK的模式更结构化,Zustand更灵活。
DevTools集成:RTK默认集成Redux DevTools,支持action历史回放、state diff查看。Zustand通过devtools中间件同样支持DevTools,但action命名需要手动指定。
中间件生态:RTK内置RTK Query实现数据获取与缓存,与store深度集成。Zustand中间件系统较简单,persist(持久化)、subscribe(订阅)满足基本需求,数据获取通常配合React Query或SWR使用。
性能优化策略对比
// Redux Toolkit: 使用Reselect进行memoized selectors
import { createSelector } from '@reduxjs/toolkit'
const selectCounter = (state: RootState) => state.counter
const selectAverageValue = createSelector(
[selectCounter],
(counter) => {
if (counter.history.length === 0) return 0
return counter.history.reduce((sum, val) => sum + val, 0) / counter.history.length
}
)
// 组件中使用
function AverageDisplay() {
const average = useAppSelector(selectAverageValue)
return <span>平均值: {average.toFixed(2)}</span>
}
// Zustand: 选择性订阅 + useShallow
import { useShallow } from 'zustand/react/shallow'
// 只在value或history变化时重渲染
function CounterStats() {
const { value, history } = useCounterStore(
useShallow((state) => ({
value: state.value,
history: state.history,
}))
)
const max = Math.max(...history)
const min = Math.min(...history)
return (
<div>
<p>当前值: {value}</p>
<p>历史最大: {max},最小: {min}</p>
</div>
)
}
项目选型建议
选择Redux Toolkit的场景:团队已有Redux经验,项目需要严格的单向数据流和可追溯的状态变更,使用RTK Query管理API数据获取,需要完整的DevTools调试能力。大型企业级应用、金融系统等对状态可预测性要求高的项目适合RTK。
选择Zustand的场景:中小型项目,团队追求快速开发,不需要严格的Flux架构约束,状态逻辑分散在各模块中。React Native应用、组件库内部状态管理、渐进式迁移项目适合Zustand。
混合使用方案:全局共享状态用RTK管理(用户信息、权限、主题配置),局部业务状态用Zustand管理(表单临时数据、UI交互状态)。两种方案在同一项目中可以共存,互不干扰。
包体积方面,Zustand约1.1KB(gzip),RTK约14KB(gzip)。对包体积敏感的移动端项目,Zustand有明显优势。但RTK的体积在整体应用bundle中占比有限,不是选型的决定性因素。
学习曲线方面,Zustand的API数量少,理解create和set即可上手。RTK需要理解slice、reducer、action、dispatch、selector、thunk等概念,学习成本更高但模式更成熟。团队规模5人以上建议统一方案,避免混用导致维护困难。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/react-zhuang-tai-guan-li-fang-an-dui-bi-reduxtoolkit-yu/