注意:本文章基于zustand v5.0.7
介绍
Zustand是一个轻量便捷的全局状态管理库,可在短时间内快速上手
对比
| 维度 |
Redux |
MobX |
Zustand |
| 样板代码 |
多(action、reducer、selector) |
少(装饰器 / makeAutoObservable) |
极少(create(set => …) 即可) |
| 异步 |
需中间件(thunk/saga) |
原生 runInAction / flow |
原生 async/await |
| 绑定组件 |
react-redux connect/useSelector |
observer 高阶组件 |
直接 useStore 钩子 |
快速上手
安装
1 2 3
| npm i zustand # npm i immer persist # 可能需要用到的中间件依赖
|
创建状态
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| interface UserStore { id: number age: number arr: number[] setId: (id: number) => void ageAdd1: () => void addNum: (n: number) => void }
const useUserStore = create<UserStore>()( (set, get) => ({ id: 0, age: 0, arr: [], setId: (id) => set({id: id}), ageAdd1: () => set(state => ({age: state.age+1})), addNum: (n) => set(state => ({arr: [...state.arr, n]})) }) )
|
注意:必须返回新的对象引用,否则会导致组件不刷新,ts下建议使用create<>()()而不是create<>()
引用状态
1 2 3 4 5 6 7 8 9 10
| const ele:React.FC = () => { const age = useUserStore(state => state.age) const ageAdd1 = useUserStore(state => state.ageAdd1) return ( <div> age: {age} <button onClick={ageAdd1}/> </div> ) }
|
状态选择
不建议使用useUserStore()直接获取整个状态对象,这会导致状态中任意属性变化都触发组件重新渲染,建议使用Selector选择状态属性:
1 2
| const id = useUserStore(state => state.id) const name = useUserStore(state => state.name)
|
每次状态变化时zustand会重新执行Selector并进行引用比较,若Selector返回值的引用变化则重新渲染相应的组件
因为zustand使用引用比较判断变化,所以直接使用结构赋值会导致State无限循环:
1 2 3
| const [id, name] = useUserStore(state => [id, name]) const {id, name} = useUserStore(state => ({id: state.id, name: state.id}))
|
若需要获取多个状态属性,应该使用useShallow:(zustand v5)
1 2 3
| const [id, name] = useUserStore(useShallow(state => [id, name])) const {id, name} = useUserStore(useShallow(state => ({id: state.id, name: state.id})))
|
shallow会对对象进行浅层属性比较以避免无限循环
使用immer中间件实现可变数据结构
安装immer
使用immer中间件
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
| import { create } from 'zustand' import { immer } from 'zustand/middleware/immer'
type State = { count: number }
type Actions = { increment: (qty: number) => void decrement: (qty: number) => void }
export const useCountStore = create<State & Actions>()( immer((set) => ({ count: 0, increment: (qty: number) => set((state) => { state.count += qty }), decrement: (qty: number) => set((state) => { state.count -= qty }), })), )
|
使用immer后修改状态不再需要返回新的state对象
使用persist中间件实现状态持久化
安装persist
使用persist
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| import { create } from 'zustand' import { persist, createJSONStorage } from 'zustand/middleware'
interface FishStore { fishes: number, addAfish: () => void }
const useFishStore = create<FishStore>()( persist( (set, get) => ({ fishes: 0, addAFish: () => set({ fishes: get().fishes + 1 }), }), { name: 'food-storage', storage: createJSONStorage(() => sessionStorage), } ) )
|
可以使用sessionStorange,localStorage(默认),cookie,indexeddb等持久化,只需在createJSONStrange中能够返回存储引擎的函数(存储引擎应具有getItem,setItem和removeItem方法)
其他问题
为什么TS下create()需要柯林化create<>()()
这是对microsoft/TypeScript#10571的变通方案
对于
1 2 3 4 5 6 7
| function case3<A, B, C>(b: string, c: boolean): A {}
example('thing');
example<number, string, boolean>('thing', true);
|
可以变通写成
1 2 3
| function case4<A>(): <B, C>(b:string, c:boolean) => A{}
example<number>()("thing", true)
|
case4现在什么都不做,只会返回一个类型为<B, C>(b:string, c:boolean) => A{}的函数,实现了A手动指定而B,C自动推断
zustand中create的定义:
1 2 3 4 5
| type Create = { <T, Mos extends [StoreMutatorIdentifier, unknown][] = []>(initializer: StateCreator<T, [], Mos>): UseBoundStore<Mutate<StoreApi<T>, Mos>>; <T>(): <Mos extends [StoreMutatorIdentifier, unknown][] = []>(initializer: StateCreator<T, [], Mos>) => UseBoundStore<Mutate<StoreApi<T>, Mos>>; }; export declare const create: Create;
|