Skip to Content

Zustand

基本使用

@/stores/cnt.ts
import { create } from "zustand"; interface ICntStore { cnt: number; incCnt: () => void; decCnt: () => void; resetCnt: () => void; getCnt: () => number; } const useCntStore = create<ICntStore>((set, get) => { return { cnt: 0, incCnt: () => set((state) => ({ cnt: state.cnt + 1 })), decCnt: () => set((state) => ({ cnt: state.cnt - 1 })), resetCnt: () => set({ cnt: 0 }), getCnt: () => get().cnt, }; }); export default useCntStore;

深层次状态

immer

import { produce } from "immer"; const data = { user: { name: "lark", age: 22 } }; const newData = produce(data, (draft) => { draft.user.age = 23; }); // {user: {name: 'lark', age: 23}}, false console.log(newData, newData === data);

zustand 使用 immer 中间件

不使用 immer 中间件
import { create } from "zustand"; interface IGroup { cnt: { female: number; male: number; }; addMale: () => void; } const useGroupStore = create<IGroup>((set) => ({ cnt: { female: 0, male: 1, }, addMale: () => set((state) => ({ cnt: { ...state.cnt, male: state.cnt.male + 1, }, })), })); export default useGroupStore;

immer 原理: Proxy 代理

const obj = { user: { name: "lark", age: 23, }, }; const isObject = (val) => typeof val === "object" && val !== null; // recipe: (draft) => void const produce = (base, recipe) => { const modified = {}; const /** @type {ProxyHandler} */ handler = { get(target, prop, receiver) { if (prop in modified) { return modified[prop]; } if (isObject(target[prop])) { return new Proxy(target[prop], handler); } return Reflect.get(target, prop, receiver); }, set(target, prop, value) { return Reflect.set(modified, prop, value); }, }; const draft = new Proxy(base, handler); recipe(draft); if (Object.keys(modified).length === 0) { return base; } return draft; }; const newObj = produce(obj, (draft) => { draft.user.name = "lark2"; draft.user.age++; }); console.log(newObj, obj);

状态切片, useShallow

  • 使用解构: setSing 时, 会导致 <First /> 组件更新; setDance 时, 也会导致 <First /> 组件更新
  • 使用状态切片: setSing 时, 会导致 <Second /> 组件更新; setDance 时, 不会导致 <Second /> 组件更新
  • 使用 useShallow 中间件: setSing 时, 会导致 <Third /> 组件更新; setDance 时, 不会导致 <Third /> 组件更新
@/stores/kun.ts
import { create } from "zustand"; interface IKun { name: string; hobbies: { sing: string; dance: string; }; setSing: (newSing: string) => void; setDance: (newDance: string) => void; } const useKunStore = create<IKun>((set) => ({ name: "kun", hobbies: { sing: "sing", dance: "dance", }, setSing: (newSing: string) => { set((state) => ({ ...state, hobbies: { ...state.hobbies, sing: newSing }, })); }, setDance: (newDance: string) => { set((state) => ({ ...state, hobbies: { ...state.hobbies, dance: newDance }, })); }, })); export default useKunStore;

中间件

  • immer 中间件
  • devtools 调试中间件
  • persist 持久化中间件
@/stores/kun.ts
import { create } from "zustand"; interface IKun { name: string; hobbies: { sing: string; dance: string; }; setSing: (newSing: string) => void; setDance: (newDance: string) => void; } const useKunStore = create<IKun>((set) => ({ name: "kun", hobbies: { sing: "sing", dance: "dance", }, setSing: (newSing: string) => { set((state) => ({ ...state, hobbies: { ...state.hobbies, sing: newSing }, })); }, setDance: (newDance: string) => { set((state) => ({ ...state, hobbies: { ...state.hobbies, dance: newDance }, })); }, })); export default useKunStore;

自定义 logger 中间件

@/middlewares/logger.js
export const logger = (fn) => (set, get, storeApi) => { const decoratedSet = (...args) => { console.log("[set] before", get()); set(...args); console.log("[set] after", get()); }; return fn(decoratedSet, get, storeApi); };

subscribe

subscribe 订阅

useStore.subscribe((state) => console.log(state) /** listener */);

subscribe 订阅: state 的任意属性改变时, 都会触发 listener 的调用

  • 组件外部订阅
  • 组件内部订阅: 需要写在 useEffect 中, 并且依赖项数组 deps 是 [] 空数组, 只会在组件挂载后订阅一次, 防止重复订阅

示例

  • 未使用 subscribe 订阅, 每次 age 改变时, 都会触发组件更新
  • 使用 subscribe 订阅, age 改变时, 不会触发组件更新; 只有 isYoung 状态改变时, 才会触发组件更新
@/stores/user.ts
import { create } from "zustand"; interface IUser { name: string; age: number; setName: (newName: string) => void; incAge: () => void; decAge: () => void; } const useUserStore = create<IUser>((set) => ({ name: "lark", age: 18, setName: (newName: string) => set(() => ({ name: newName })), incAge: () => set((state) => ({ age: state.age + 1 })), decAge: () => set((state) => ({ age: state.age - 1 })), })); export default useUserStore;

subscribe + subscribeWithSelector 中间件

subscribe + subscribeWithSelector 中间件: state 的指定属性改变时, 才会触发 listener 的调用

示例

  • 未使用 subscribeWithSelector 中间件: state 的任意属性改变时, 都会触发 listener 的调用
  • 使用 subscribeWithSelector 中间件: 仅 state 的 age 属性改变时, 才会触发 listener 的调用
@/stores/user.ts
import { create } from "zustand"; import { subscribeWithSelector } from "zustand/middleware"; interface IUser { name: string; age: number; setName: (newName: string) => void; incAge: () => void; decAge: () => void; } const useUserStore = create<IUser>()( subscribeWithSelector((set) => ({ name: "lark", age: 18, setName: (newName: string) => set(() => ({ name: newName })), incAge: () => set((state) => ({ age: state.age + 1 })), decAge: () => set((state) => ({ age: state.age - 1 })), })), ); export default useUserStore;
Last updated on