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
|
import {create} from 'zustand'
type CounterState = { count: number, add: () => void minus: () => void }
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
| import {atom} from 'jotai'
export const ageAtom = atom(0)
|
1 2 3 4 5
| import {atom} from 'jotai'
export const nameAtom = atom('')
|
1 2 3 4 5 6 7 8
| 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来使用。
实现一个计数器功能:
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', initialState: { value: 0, }, reducers: { increment: (state) => { state.value += 1; }, decrement: (state) => { state.value -= 1; }, incrementByAmount: (state, action) => { state.value += action.payload; }, }, });
export const {increment, decrement, incrementByAmount} = counterSlice.actions;
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, }, });
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';
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', 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+人大型团队、超复杂项目更适配 |