react状态管理方案

zustand

优势:

  • 极简的 api,较低的学习成本。只需通过 create 函数创建 store,直接定义状态和修改状态的方法,无需冗余模板。
  • 更灵活的状态访问方式。支持选择性订阅(只监听需要的状态字段,减少不必要的重渲染,性能好)。
    const count = useStore((state) => state.count)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// src/stores/useCounter.ts

// 引入 zustand 库
import {create} from 'zustand'

type CounterState = {
count: number,
add: () => void
minus: () => void
}

// 创建一个 zustand 状态管理 store
const useCounter = create<CounterState>((set) => (
{
count: 0,
add: () => set((state: any) => ({count: state.count + 1})),
minus: () => set((state: any) => ({
count: state.count - 1
}))

}
))

export default useCounter

在页面上使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import useCounter from '@/stores/useCounter'

function App() {
const {count, add, minus} = useCounter()
return (
<>
<div>
{count}
</div>
<button onClick={add}>
add
</button>
<button onClick={minus}>
minus
</button>
</>
)
}

jotai

jotai 的核心是 atom,atom 是 jotai 中最基本的状态单位,可以是基本类型,也可以是对象类型。atom 之间可以有依赖关系,当一个 atom
的值发生变化时,所有依赖它的 atom 都会重新计算。

1
2
3
4
// AgeAtom.ts
import {atom} from 'jotai'

export const ageAtom = atom(0)
1
2
3
4
5
// NameAtom.ts
import {atom} from 'jotai'

export const nameAtom = atom('')

1
2
3
4
5
6
7
8
// InfoAtom.ts
import {atom} from 'jotai'
import {ageAtom, nameAtom} from './Atom'

export const infoAtom = atom((get) => ({
age: get(ageAtom),
name: get(nameAtom)
}))

在页面上使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import {useAtomValue, useSetAtom} from 'jotai'
import {infoAtom, ageAtom, nameAtom} from './Atom'

function App() {
const info = useAtomValue(infoAtom)
const setAge = useSetAtom(ageAtom)
const setName = useSetAtom(nameAtom)
return (
<>
<div>
{info.age}
</div>
<div>
{info.name}
</div>
<button onClick={() => setAge((age) => age + 1)}>+</button>
<button onClick={() => setName((name) => name + 'hualu')}>add name</button>
</>
)
}

redux

redux搭配redux-toolkit来使用。

实现一个计数器功能:

第一步:安装 Redux Toolkit 和 React-Redux绑定库

npm install @reduxjs/toolkit react-redux

第二步:创建 Slice (状态切片)

Slice 是 RTK 的核心概念,它包含了一部分状态初始值、reducer 函数以及自动生成的 actions。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import {createSlice} from '@reduxjs/toolkit';

const counterSlice = createSlice({
name: 'counter', // 命名空间,用于生成 action type
initialState: {
value: 0,
},
reducers: {
// 同步操作:可以直接修改 state,Immer 会处理不可变更新
increment: (state) => {
state.value += 1;
},
decrement: (state) => {
state.value -= 1;
},
incrementByAmount: (state, action) => {
state.value += action.payload;
},
},
});

// 导出 actions
export const {increment, decrement, incrementByAmount} = counterSlice.actions;

// 导出 reducer
export default counterSlice.reducer;

第三步:配置store

创建全局唯一的 Store,并将 slice 的 reducer 注册进去。

创建文件 src/app/store.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
import {configureStore} from '@reduxjs/toolkit';
import counterReducer from '../features/counter/counterSlice';

const store = configureStore({
reducer: {
counter: counterReducer,
// 如果有其他 slice,继续添加
// user: userReducer,
},
});

export default store;

第四步:将 Store 注入 React 应用

使用 Provider 组件将 store 传递给整个 React 组件树。

修改文件 src/index.js (或 main.jsx):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import React from 'react';
import ReactDOM from 'react-dom/client';
import {Provider} from 'react-redux';
import store from './app/store';
import App from './App';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<Provider store={store}>
<App/>
</Provider>
</React.StrictMode>
);

第五步:在组件中使用 State 和 Dispatch

使用 useSelector 读取状态,使用 useDispatch 触发 action。

创建文件 src/features/counter/Counter.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import React, {useState} from 'react';
import {useSelector, useDispatch} from 'react-redux';
import {increment, decrement, incrementByAmount} from './counterSlice';

export function Counter() {
const count = useSelector((state) => state.counter.value);
const dispatch = useDispatch();
const [incrementAmount, setIncrementAmount] = useState('2');

return (
<div>
<div className="counter">
<button
aria-label="Increment value"
onClick={() => dispatch(increment())}
>
+
</button>
<span>{count}</span>
<button
aria-label="Decrement value"
onClick={() => dispatch(decrement())}
>
-
</button>
</div>
<div className="controls">
<input
type="number"
value={incrementAmount}
onChange={(e) => setIncrementAmount(e.target.value)}
/>
<button
onClick={() =>
dispatch(incrementByAmount(Number(incrementAmount) || 0))
}
>
Add Amount
</button>
</div>
</div>
);
}

处理异步逻辑 (Thunk)

在实际项目中,经常需要处理 API 请求。RTK 内置了 createAsyncThunk 来简化异步操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import {createSlice, createAsyncThunk} from '@reduxjs/toolkit';
import axios from 'axios';

// 定义异步 thunk
export const fetchUsers = createAsyncThunk('users/fetchUsers', async () => {
const response = await axios.get('https://jsonplaceholder.typicode.com/users');
return response.data;
});

const usersSlice = createSlice({
name: 'users',
initialState: {
items: [],
status: 'idle', // 'idle' | 'loading' | 'succeeded' | 'failed'
error: null,
},
reducers: {},
extraReducers: (builder) => {
builder
.addCase(fetchUsers.pending, (state) => {
state.status = 'loading';
})
.addCase(fetchUsers.fulfilled, (state, action) => {
state.status = 'succeeded';
state.items = action.payload;
})
.addCase(fetchUsers.rejected, (state, action) => {
state.status = 'failed';
state.error = action.error.message;
});
},
});

export default usersSlice.reducer;

总结

优先推荐使用zustand,zustand完全够用

维度 Zustand Redux Toolkit
‌Bundle体积‌ 仅 ‌1.1 KB‌,几乎无额外负担 约 ‌11.1 KB‌,体积是Zustand的10倍
‌学习曲线‌ 极低,几分钟就能跑通一个完整Store 中高,需要理解Slice、Reducer、Dispatch等多个概念
‌样板代码‌ 几乎为零,一行代码就能创建基础Store 中等,需要完整配置Store、注册Reducer等流程
‌异步处理‌ 直接在Store函数里写async逻辑即可 需要借助createAsyncThunk或RTK Query封装
‌适配团队规模‌ 1-10人小团队/个人项目更顺手 5-50+人大型团队、超复杂项目更适配

react状态管理方案
https://zouhualu.github.io/20240915/react状态管理方案/
作者
花鹿
发布于
2024年9月15日
许可协议