Zustand - Set

概念

在调用 create() 或者 createWithEqualityFn(),要传入一个工厂函数,Zustand 会为这个工厂函数注入两个工具参数

const useStore = create<StoreState>()(
  (set, get, api) => { /* 这里 return 状态对象 */ }
);
参数作用
set唯一推荐 的更新状态入口。可批量更新、支持回调、可选 replace 模式。
get读取当前最新的 store 状态。
apistore 实例本身,包含 setState / getState / subscribe 等底层方法。

set 的类型签名

type SetState<S> = {
  (partial: Partial<S> | ((state: S) => Partial<S | void>), replace?: boolean): void
}

常用用法

局部更新

increase: () => set((state) => ({ count: state.count + 1 })),
  • 传入 对象片段 或 返回对象的回调,Zustand 会把它 浅合并 (Object.assign) 到旧状态
  • 组件只会因 “真正变更的字段” 触发渲染

一次更新多个字段

set((state) => ({
  bears: state.bears + 1,
  lastUpdated: Date.now(),
}))

整体替换

// 把整个 store 替换成一个全新的对象
resetAll: () => set({ bears: 0, fish: 0 }, true),

当第二个参数为 true 时,Zustand 不再做浅合并,而是把原状态整体替换成新的对象

异步或带副作用的场景

fetchUser: async (id: string) => {
  set({ loading: true });
  try {
    const user = await api.get(`/user/${id}`);
    set({ user, loading: false });
  } catch (e) {
    set({ error: e, loading: false });
  }
},

set 没有对异步做限制:可以 await 或在异步回调里调用它。

Zustand 的柯里化函数问题

在了解中间件之前需要了解什么是柯里化函数

create<AuthState>()( /* 中间件包裹函数 */ ) 是一种函数柯里化(curried function)+ 泛型传参的组合语法

create 的定义简化后大致是 function create<T>(): (initializer: StateCreator<T>) => UseBoundStore<T>

也可以看成

const createStore = <T>() => (initializer: (set, get) => T) => {
  return /* Hook that uses that initializer */;
}
  • create<T>() 先把泛型类型 T 提交进来
  • 之后返回一个函数,这个函数接受初始化函数(即 (set, get) => {...}
  • 最后返回一个可用的 store hook(如 UseBoundStore

为什么不是 create<AuthState>(...)

因为 create(…) 需要泛型信息,但不能直接从 (…) 中的中间件推导出来泛型参数。所以必须先传入 <AuthState>,再调用一次函数传入初始化器

// ❌ 无法推导中间层中间件的返回值类型
create<AuthState>(
  persist(...)   // ⛔ TypeScript 报错
);

// ✅ 拆成两步,类型信息先注入,再交给中间件返回 store 初始化逻辑
create<AuthState>()(
  persist(...)   // ✅ 类型可推导
);

这是由于 TypeScript 的 类型推导边界 问题

中间件产生情况

中间件(devtools / persist / immer …)都是高阶函数

// 形态示意
const wrappedInitializer = devtools(
  persist(
    immer(originalInitializer),
    persistOptions
  ),
  devtoolsOptions
);

输入 原始 initializer,输出 “增强版 initializer”

因此 必须出现在 “initializer 要交给 create 的位置” —— 也就是 第二个括号

运行时情况

  • 第一步:等待 init 的函数 —— 完全无运行时开销
const _createStore = create<AuthState>(); 
// _createStore 的类型: (init: StateCreator<AuthState>) => UseBoundStore<AuthState>
  • 第二步
    • 中间件先把真 initializer 包装增强
    • 完成后交给 _createStore
    • 最终得到绑定 React 的 useAuth Hook
const useAuth = _createStore(
  devtools(persist((set) => ({ /* ... */ })))
);

总结而言,两对括号只是 类型和调用顺序的语法糖,并不会额外执行 create 两次

中间件的交互

中间件 set 的关系
devtoolsWrapper 会把 set 改写成 set(devtoolsWrapper(…)),无需改变写法;每次调用 set 都能在 Redux DevTools 里看到 action。
persistset 仍然用浅合并;被标记为 partialize 的字段会落盘。
subscribeWithSelector不改 set,但能让外部监听到每次 set 的结果 slice。
immer把传给 set 的回调参数 state 变成可变草稿,回调结束后由 Immer 产出不可变的新对象

devtools

让 Zustand 自动出现在 Redux DevTools,包装 set,在每次状态更新时向 Redux DevTools 发送一条 action 记录

import { create } from 'zustand';
import { devtools } from 'zustand/middleware';

interface CounterState {
  count: number;
  inc: () => void;
  dec: () => void;
}

export const useCounter = create<CounterState>()(
  devtools(
    (set) => ({
      count: 0,
      inc: () => set((s) => ({ count: s.count + 1 }), false, 'inc'), // 手动命名 action
      dec: () => set((s) => ({ count: s.count - 1 }), false, 'dec'),
    }),
    { name: 'CounterStore' },
  ),
);
  • 第三个参数 ‘inc’ / ‘dec’ 是 自定义 actionType,方便在 DevTools 面板里区分
  • 如果想在生产环境关闭 DevTools 上报,process.env.NODE_ENV === 'development' ? devtools(...) : (fn) => fn

persist

把状态持久化到 LocalStorage、IndexedDB 等,拦截每次 set 后的最新状态,将指定字段序列化保存;页面刷新后自动复原

  • 默认使用 localStorage (Web)、AsyncStorage (React Native)
  • 通过 partialize 精确控制保存范围
import { create } from 'zustand';
import { persist } from 'zustand/middleware';

interface AuthState {
  user: string | null;
  token: string | null;
  setAuth: (u: string, t: string) => void;
  logout: () => void;
}

export const useAuth = create<AuthState>()(
  persist(
    (set) => ({
      user: null,
      token: null,
      setAuth: (u, t) => set({ user: u, token: t }),
      logout: () => set({ user: null, token: null }),
    }),
    {
      name: 'auth-storage',
      partialize: (state) => ({ token: state.token }), // 仅保存 token
      version: 1,                                      // 支持升级迁移
      // storage: createJSONStorage(() => sessionStorage) // 如需改存储介质
    },
  ),
);
  • 仅 token 被序列化;user 页面刷新后会回到 null
  • 当调用 setAuthlogout 时,persist 会在内部二次 set 到存储

immer

把传给 set 的回调参数 state 变成可变草稿,回调结束后由 Immer 产出不可变的新对象

import { create } from 'zustand';
import { immer } from 'zustand/middleware/immer';

interface Todo {
  id: number;
  done: boolean;
  text: string;
}
interface TodoState {
  todos: Todo[];
  toggle: (id: number) => void;
}

export const useTodo = create<TodoState>()(
  immer((set) => ({
    todos: [],
    toggle: (id) =>
      set((state) => {
        const t = state.todos.find((i) => i.id === id);
        if (t) t.done = !t.done;        // ← 像写普通 JS 一样“改”数据
      }),
  })),
);
  • set 回调里直接 修改 state.todosImmer 自动生成新状态
  • 如果你想整体替换(例如排序),仍可直接赋新数组:state.todos = newArr

中间件执行顺序

import { create } from 'zustand';
import { immer } from 'zustand/middleware/immer';
import { persist } from 'zustand/middleware';
import { devtools } from 'zustand/middleware';

// ⬇️ 最推荐的组合写法
export const usePrice = create<PriceState>()(
  devtools(
    persist(
      immer((set) => ({
        price: 0,
        inc: (n = 1) => set((s) => void (s.price += n)),
        reset: () => set({ price: 0 }),
      })),
      {
        name: 'price-storage',
        partialize: (s) => ({ price: s.price }),
      },
    ),
    { name: 'PriceStore' },
  ),
);
  • immer 先帮我们把深层修改转成不可变新对象
  • 新状态传给 persist,它把选中的字段序列化落盘
  • 最后 devtools 把这次 set 记录到 Redux DevTools

综合案例

import { create } from 'zustand';
import { devtools, persist } from 'zustand/middleware';
import { immer } from 'zustand/middleware/immer';

interface PriceState {
  price: number;
  history: number[];
  increase: (n?: number) => void;
  undo: () => void;
}

export const usePriceState = create<PriceState>()(
  devtools(
    persist(
      immer((set, get) => ({
        price: 0,
        history: [],
        increase: (n = 1) =>
          set((state) => {
            state.history.push(state.price); // Immer 可写
            state.price += n;
          }),
        undo: () =>
          set((state) => {
            const prev = state.history.pop();
            if (prev !== undefined) state.price = prev;
          }),
      })),
      { name: 'price-storage', partialize: s => ({ price: s.price }) },
    )
  )
);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值