zustand

zustand

热门

LobeHub Zustand 存储约定。在编辑 src/store、存储切片、公共/内部动作、调度动作、flattenActions、乐观更新、选择器、映射或类动作迁移时使用。

8.1万Star
1.6万Fork
更新于 2026/7/23
SKILL.md
readonly只读
name
zustand
description

LobeHub Zustand 存储约定。在编辑 src/store、存储切片、公共/内部动作、调度动作、flattenActions、乐观更新、选择器、映射或类动作迁移时使用。

LobeHub Zustand 状态管理

动作类型层次

1. 公共动作

UI 组件使用的主要接口:

  • 命名:动词形式(createTopicsendMessage
  • 职责:参数验证、流程编排

2. 内部动作(internal_*

核心业务逻辑实现:

  • 命名:internal_ 前缀(internal_createTopic
  • 职责:乐观更新、服务调用、错误处理
  • 不应由 UI 直接调用

3. 调度方法(internal_dispatch*

状态更新处理器:

  • 命名:internal_dispatch + 实体(internal_dispatchTopic
  • 职责:调用 reducer、更新存储

何时使用 Reducer 与简单 set

使用 Reducer 模式:

  • 管理对象列表/映射(messagesMaptopicMaps
  • 乐观更新
  • 复杂状态转换

使用简单 set

  • 切换布尔值
  • 更新简单值
  • 设置单个状态字段

乐观更新模式

internal_createTopic: async (params) => {
  const tmpId = Date.now().toString();

  // 1. 立即更新前端(乐观)
  get().internal_dispatchTopic(
    { type: 'addTopic', value: { ...params, id: tmpId } },
    'internal_createTopic'
  );

  // 2. 调用后端服务
  const topicId = await topicService.createTopic(params);

  // 3. 刷新以保持一致性
  await get().refreshTopic();
  return topicId;
},

删除操作:不要使用乐观更新(破坏性操作,恢复复杂)

命名约定

动作:

  • 公共:createTopicsendMessage

  • 内部:internal_createTopicinternal_updateMessageContent

  • 调度:internal_dispatchTopic
    状态:

  • ID 数组:topicEditingIds

  • 映射:topicMapsmessagesMap

  • 当前:activeTopicId

  • 初始化标志:topicsInit

详细指南

  • 动作模式:references/action-patterns.md
  • 切片组织:references/slice-organization.md

基于类的动作实现

我们正在将切片从普通的 StateCreator 对象迁移到基于类的动作

模式

  • 定义一个类,封装动作并在构造函数中接收 (set, get, api)
  • 使用 #private 字段(例如 #set#get)避免泄露内部细节。
  • 优先使用共享类型辅助:
    • 来自 @/store/typesStoreSetter<T> 用于 set
    • Pick<ActionImpl, keyof ActionImpl> 仅暴露公共方法。
  • 导出一个 create*Slice 辅助函数,返回类实例。
type Setter = StoreSetter<HomeStore>;
export const createRecentSlice = (set: Setter, get: () => HomeStore, _api?: unknown) =>
  new RecentActionImpl(set, get, _api);

export class RecentActionImpl {
  readonly #get: () => HomeStore;
  readonly #set: Setter;

  constructor(set: Setter, get: () => HomeStore, _api?: unknown) {
    void _api;
    this.#set = set;
    this.#get = get;
  }

  useFetchRecentTopics = () => {
    // ...
  };
}

export type RecentAction = Pick<RecentActionImpl, keyof RecentActionImpl>;

组合

  • 在存储文件中,使用 flattenActions 合并类实例(不要展开类实例)。
  • flattenActions 将方法绑定到原始类实例,并支持原型方法和类字段。
const createStore: StateCreator<HomeStore, [['zustand/devtools', never]]> = (...params) => ({
  ...initialState,
  ...flattenActions<HomeStoreAction>([
    createRecentSlice(...params),
    createHomeInputSlice(...params),
  ]),
});

多类切片

  • 对于需要多个动作类的大型切片,在切片入口中使用 flattenActions 组合它们。
  • 如果需要组合多个类并隐藏私有字段,使用本地的 PublicActions<T> 辅助类型。
type PublicActions<T> = { [K in keyof T]: T[K] };

export type ChatGroupAction = PublicActions<
  ChatGroupInternalAction & ChatGroupLifecycleAction & ChatGroupMemberAction & ChatGroupCurdAction
>;

export const chatGroupAction: StateCreator<
  ChatGroupStore,
  [['zustand/devtools', never]],
  [],
  ChatGroupAction
> = (...params) =>
  flattenActions<ChatGroupAction>([
    new ChatGroupInternalAction(...params),
    new ChatGroupLifecycleAction(...params),
    new ChatGroupMemberAction(...params),
    new ChatGroupCurdAction(...params),
  ]);

存储访问类型

  • 对于依赖于其他类中动作的类方法,定义显式的存储增强:
    • ChatGroupStoreWithSwitchTopic 用于生命周期 switchTopic
    • ChatGroupStoreWithRefresh 用于成员刷新
    • ChatGroupStoreWithInternal 用于 CRUD internal_dispatchChatGroup

当前不需要 set 的切片

当切片不写入本地状态时(例如,它委托给另一个存储或仅运行钩子),删除 #set 并将构造函数参数标记为 _set,使用 void _set 保持 (set, get, api) 形状:

export class ToolActionImpl {
  readonly #get: () => ConversationStore;

  constructor(_set: Setter, get: () => ConversationStore, _api?: unknown) {
    void _set;
    void _api;
    this.#get = get;
  }

  approveToolCall = async (id: string) => {
    const { context, hooks } = this.#get();
    await useChatStore.getState().approveToolCalling(id, '', context);
    hooks.onToolCallComplete?.(id, undefined);
  };
}
  • #set 未使用时删除它;当后续编辑需要 set 时恢复——重新添加没有成本。
  • 不要为不写入状态的切片添加 setNamespace
  • 迁移期间不要同时保留旧的切片对象和类动作。