SKILL.md
readonly只读
name
zustand
description
LobeHub Zustand 存储约定。在编辑 src/store、存储切片、公共/内部动作、调度动作、flattenActions、乐观更新、选择器、映射或类动作迁移时使用。
LobeHub Zustand 状态管理
动作类型层次
1. 公共动作
UI 组件使用的主要接口:
- 命名:动词形式(
createTopic、sendMessage) - 职责:参数验证、流程编排
2. 内部动作(internal_*)
核心业务逻辑实现:
- 命名:
internal_前缀(internal_createTopic) - 职责:乐观更新、服务调用、错误处理
- 不应由 UI 直接调用
3. 调度方法(internal_dispatch*)
状态更新处理器:
- 命名:
internal_dispatch+ 实体(internal_dispatchTopic) - 职责:调用 reducer、更新存储
何时使用 Reducer 与简单 set
使用 Reducer 模式:
- 管理对象列表/映射(
messagesMap、topicMaps) - 乐观更新
- 复杂状态转换
使用简单 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;
},
删除操作:不要使用乐观更新(破坏性操作,恢复复杂)
命名约定
动作:
-
公共:
createTopic、sendMessage -
内部:
internal_createTopic、internal_updateMessageContent -
调度:
internal_dispatchTopic
状态: -
ID 数组:
topicEditingIds -
映射:
topicMaps、messagesMap -
当前:
activeTopicId -
初始化标志:
topicsInit
详细指南
- 动作模式:
references/action-patterns.md - 切片组织:
references/slice-organization.md
基于类的动作实现
我们正在将切片从普通的 StateCreator 对象迁移到基于类的动作。
模式
- 定义一个类,封装动作并在构造函数中接收
(set, get, api)。 - 使用
#private字段(例如#set、#get)避免泄露内部细节。 - 优先使用共享类型辅助:
- 来自
@/store/types的StoreSetter<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用于生命周期switchTopicChatGroupStoreWithRefresh用于成员刷新ChatGroupStoreWithInternal用于 CRUDinternal_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。 - 迁移期间不要同时保留旧的切片对象和类动作。






