什么是 Zustand
Zustand 是一个小巧、快速且可扩展的状态管理解决方案,基于简化的 flux 原则 。它提供了基于 hooks 的舒适 API,没有样板代码,也不强制使用特定模式 。
安装
基础使用
1. 创建 Store
使用 create 函数创建一个 store,它会返回一个 React Hook:
1 2 3 4 5 6 7
| import { create } from 'zustand'
const useBearStore = create((set) => ({ bears: 0, increasePopulation: () => set((state) => ({ bears: state.bears + 1 })), removeAllBears: () => set({ bears: 0 }), }))
|
2. 在组件中使用
直接在任何组件中使用 hook,无需 Provider 包裹 :
1 2 3 4 5 6 7 8 9
| function BearCounter() { const bears = useBearStore((state) => state.bears) return <h1>{bears} around here ...</h1> }
function Controls() { const increasePopulation = useBearStore((state) => state.increasePopulation) return <button onClick={increasePopulation}>one up</button> }
|
核心概念
选择状态切片
使用 selector 函数选择特定的状态,组件只会在选中的状态变化时重新渲染 :
1 2
| const nuts = useBearStore((state) => state.nuts) const honey = useBearStore((state) => state.honey)
|
异步 Actions
直接在 action 中使用 async/await,Zustand 不关心你的 action 是否异步 :
1 2 3 4 5 6 7
| const useFishStore = create((set) => ({ fishies: {}, fetch: async (pond) => { const response = await fetch(pond) set({ fishies: await response.json() }) }, }))
|
推荐的做法是将动作和状态放在存储中(让动作与状态位于同一位置)。
1 2 3 4 5 6
| export const useBoundStore = create((set) => ({ count: 0, text: 'hello', inc: () => set((state) => ({ count: state.count + 1 })), setText: (text) => set({ text }), }))
|
这将创建一个包含数据和操作的自包含存储。
另一种方法是在模块级别定义操作,使其位于存储外部。
1 2 3 4 5 6 7 8 9
| export const useBoundStore = create(() => ({ count: 0, text: 'hello', }))
export const inc = () => useBoundStore.setState((state) => ({ count: state.count + 1 }))
export const setText = (text) => useBoundStore.setState({ text })
|
这具有以下几个优势:
无需钩子即可调用操作
便于代码拆分
虽然这种模式没有明显缺点,但有些人可能更倾向于采用同位置放置模式,因为它具有封装特性
完整示例
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 { devtools, persist, subscribeWithSelector } from "zustand/middleware";
const initialFoodValue = { fish: 0, mouse: 0, };
export const useFoodStore = create<typeof initialFoodValue>()( devtools( subscribeWithSelector( persist(() => initialFoodValue, { name: "food store" }) ), { name: "food store" } ) );
export const addOneFish = () => useFoodStore.setState((state) => ({ fish: state.fish + 1 }));
export const removeOneFish = () => useFoodStore.setState((state) => ({ fish: state.fish - 1 }));
export const removeAllFish = () => useFoodStore.setState({ fish: 0 });
|
immer -> devtools -> subscribeWithSelector -> persist 顺序
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 45 46 47 48 49 50 51 52 53 54 55 56 57 58
| import { StateCreator, create } from "zustand"; import { immer } from "zustand/middleware/immer"; import { createSelectors } from "../utils/createSelectors"; import { devtools, persist, subscribeWithSelector } from "zustand/middleware";
type TCatStoreState = { cats: { bigCats: number; smallCats: number; }; increaseBigCats: () => void; increaseSmallCats: () => void; summary: () => void; };
const createCatSlice: StateCreator< TCatStoreState, [ ["zustand/immer", never], ["zustand/devtools", unknown], ["zustand/subscribeWithSelector", never], ["zustand/persist", unknown] ] > = (set, get) => ({ cats: { bigCats: 0, smallCats: 0, }, increaseBigCats: () => set((state) => { state.cats.bigCats++; }), increaseSmallCats: () => set((state) => { state.cats.smallCats++; }), summary: () => { const total = get().cats.bigCats + get().cats.smallCats; return `There are ${total} cats in total. `; }, }); export const useCatStore = createSelectors( create<TCatStoreState>()( immer( devtools( subscribeWithSelector( persist(createCatSlice, { name: "cat store", }) ), { enabled: true, name: "cat store", } ) ) ) );
|
1. 使用 Slices 模式组织大型 Store
当 store 变得庞大时,可以将其分割成更小的模块:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| export const createFishSlice = (set) => ({ fishes: 0, addFish: () => set((state) => ({ fishes: state.fishes + 1 })), })
export const createBearSlice = (set) => ({ bears: 0, addBear: () => set((state) => ({ bears: state.bears + 1 })), })
import { create } from 'zustand' export const useBoundStore = create((...a) => ({ ...createBearSlice(...a), ...createFishSlice(...a), }))
|
2. 使用中间件增强功能
Persist 中间件 - 持久化状态
将状态保存到 localStorage 或其他存储引擎:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import { create } from 'zustand' import { persist, createJSONStorage } from 'zustand/middleware'
const useFishStore = create( persist( (set, get) => ({ fishes: 0, addAFish: () => set({ fishes: get().fishes + 1 }), }), { name: 'food-storage', storage: createJSONStorage(() => sessionStorage), }, ), )
|
使用 Redux DevTools 调试状态变化:
1 2 3 4 5
| import { devtools } from 'zustand/middleware'
const usePlainStore = create(devtools((set) => ({ })))
|
3. TypeScript 使用
使用 TypeScript 时,需要使用 create<State>()() 的双括号语法 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| interface BearState { bears: number increase: (by: number) => void }
const useBearStore = create<BearState>()( devtools( persist( (set) => ({ bears: 0, increase: (by) => set((state) => ({ bears: state.bears + by })), }), { name: 'bear-storage' }, ), ), )
|
4. React Context 集成
对于需要依赖注入或使用组件 props 初始化 store 的场景,推荐使用 vanilla store 配合 React Context:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| import { createContext, useContext } from 'react' import { createStore, useStore } from 'zustand'
const store = createStore(...) const StoreContext = createContext()
const App = () => ( <StoreContext.Provider value={store}> ... </StoreContext.Provider> )
const Component = () => { const store = useContext(StoreContext) const slice = useStore(store, selector) ... }
|
5. 单一 Store 架构
推荐使用单一 store 架构,将全局状态集中在一个 store 中 。对于大型应用,使用 slices 模式来实现模块化。
6. 性能优化
- 使用
useShallow 进行浅比较,避免不必要的重渲染
- 使用精确的 selector 只订阅需要的状态
- 对于频繁变化的状态,考虑使用
subscribeWithSelector 中间件
Notes
Zustand 相比 Redux 的优势包括:更少的样板代码、不需要 Context Provider、基于 hooks 的 API、支持瞬态更新(不触发渲染)。相比 React Context,它提供了更少的样板代码、只在变化时渲染组件、集中式的基于 action 的状态管理。
更多详细文档可以访问官方文档。
Wiki pages you might want to explore: