mirror of
https://github.com/lobehub/lobe-chat.git
synced 2025-12-20 01:12:52 +08:00
Some checks failed
Upstream Sync / Sync latest commits from upstream repo (push) Has been cancelled
Release CI / Release (push) Has been cancelled
Test CI / test (push) Has been cancelled
Lighthouse Badger / LobeChat | Chat (push) Has been cancelled
Lighthouse Badger / LobeChat | Market (push) Has been cancelled
Issue Close Require / issue-check-inactive (push) Has been cancelled
Issue Close Require / issue-close-require (push) Has been cancelled
Daily i18n Update / update-i18n (push) Has been cancelled
301 lines
8.3 KiB
Plaintext
301 lines
8.3 KiB
Plaintext
---
|
||
description:
|
||
globs: src/store/**
|
||
alwaysApply: false
|
||
---
|
||
# LobeChat Zustand Store Slice 组织架构
|
||
|
||
本文档描述了 LobeChat 项目中 Zustand Store 的模块化 Slice 组织方式,展示如何通过分片架构管理复杂的应用状态。
|
||
|
||
## 顶层 Store 结构
|
||
|
||
LobeChat 的 `chat` store (`src/store/chat/`) 采用模块化的 slice 结构来组织状态和逻辑。
|
||
|
||
### 关键聚合文件
|
||
|
||
- `src/store/chat/initialState.ts`: 聚合所有 slice 的初始状态
|
||
- `src/store/chat/store.ts`: 定义顶层的 `ChatStore`,组合所有 slice 的 actions
|
||
- `src/store/chat/selectors.ts`: 统一导出所有 slice 的 selectors
|
||
- `src/store/chat/helpers.ts`: 提供聊天相关的辅助函数
|
||
|
||
### Store 聚合模式
|
||
|
||
```typescript
|
||
// src/store/chat/initialState.ts
|
||
import { ChatTopicState, initialTopicState } from './slices/topic/initialState';
|
||
import { ChatMessageState, initialMessageState } from './slices/message/initialState';
|
||
import { ChatAIChatState, initialAiChatState } from './slices/aiChat/initialState';
|
||
|
||
export type ChatStoreState = ChatTopicState &
|
||
ChatMessageState &
|
||
ChatAIChatState &
|
||
// ...其他 slice states
|
||
|
||
export const initialState: ChatStoreState = {
|
||
...initialMessageState,
|
||
...initialTopicState,
|
||
...initialAiChatState,
|
||
// ...其他 initial slice states
|
||
};
|
||
```
|
||
|
||
```typescript
|
||
// src/store/chat/store.ts
|
||
import { ChatMessageAction, chatMessage } from './slices/message/action';
|
||
import { ChatTopicAction, chatTopic } from './slices/topic/action';
|
||
import { ChatAIChatAction, chatAiChat } from './slices/aiChat/actions';
|
||
|
||
export interface ChatStoreAction
|
||
extends ChatMessageAction,
|
||
ChatTopicAction,
|
||
ChatAIChatAction,
|
||
// ...其他 slice actions
|
||
|
||
const createStore: StateCreator<ChatStore, [['zustand/devtools', never]]> = (...params) => ({
|
||
...initialState,
|
||
...chatMessage(...params),
|
||
...chatTopic(...params),
|
||
...chatAiChat(...params),
|
||
// ...其他 slice action creators
|
||
});
|
||
|
||
export const useChatStore = createWithEqualityFn<ChatStore>()(
|
||
subscribeWithSelector(devtools(createStore)),
|
||
shallow,
|
||
);
|
||
```
|
||
|
||
## 单个 Slice 的标准结构
|
||
|
||
每个 slice 位于 `src/store/chat/slices/[sliceName]/` 目录下:
|
||
|
||
```
|
||
src/store/chat/slices/
|
||
└── [sliceName]/ # 例如 message, topic, aiChat, builtinTool
|
||
├── action.ts # 定义 actions (或者是一个 actions/ 目录)
|
||
├── initialState.ts # 定义 state 结构和初始值
|
||
├── reducer.ts # (可选) 如果使用 reducer 模式
|
||
├── selectors.ts # 定义 selectors
|
||
└── index.ts # (可选) 重新导出模块内容
|
||
```
|
||
|
||
### 文件职责说明
|
||
|
||
1. `initialState.ts`:
|
||
- 定义 slice 的 TypeScript 状态接口
|
||
- 提供初始状态默认值
|
||
|
||
```typescript
|
||
// 典型的 initialState.ts 结构
|
||
export interface ChatTopicState {
|
||
activeTopicId?: string;
|
||
topicMaps: Record<string, ChatTopic[]>; // 核心数据结构
|
||
topicsInit: boolean;
|
||
topicLoadingIds: string[];
|
||
// ...其他状态字段
|
||
}
|
||
|
||
export const initialTopicState: ChatTopicState = {
|
||
activeTopicId: undefined,
|
||
topicMaps: {},
|
||
topicsInit: false,
|
||
topicLoadingIds: [],
|
||
// ...其他初始值
|
||
};
|
||
```
|
||
|
||
2. `reducer.ts` (复杂状态使用):
|
||
- 定义纯函数 reducer,处理同步状态转换
|
||
- 使用 `immer` 确保不可变更新
|
||
|
||
```typescript
|
||
// 典型的 reducer.ts 结构
|
||
import { produce } from 'immer';
|
||
|
||
interface AddChatTopicAction {
|
||
type: 'addTopic';
|
||
value: CreateTopicParams & { id?: string };
|
||
}
|
||
|
||
interface UpdateChatTopicAction {
|
||
id: string;
|
||
type: 'updateTopic';
|
||
value: Partial<ChatTopic>;
|
||
}
|
||
|
||
export type ChatTopicDispatch = AddChatTopicAction | UpdateChatTopicAction;
|
||
|
||
export const topicReducer = (state: ChatTopic[] = [], payload: ChatTopicDispatch): ChatTopic[] => {
|
||
switch (payload.type) {
|
||
case 'addTopic': {
|
||
return produce(state, (draftState) => {
|
||
draftState.unshift({
|
||
...payload.value,
|
||
id: payload.value.id ?? Date.now().toString(),
|
||
createdAt: Date.now(),
|
||
});
|
||
});
|
||
}
|
||
case 'updateTopic': {
|
||
return produce(state, (draftState) => {
|
||
const index = draftState.findIndex((topic) => topic.id === payload.id);
|
||
if (index !== -1) {
|
||
draftState[index] = { ...draftState[index], ...payload.value };
|
||
}
|
||
});
|
||
}
|
||
default:
|
||
return state;
|
||
}
|
||
};
|
||
```
|
||
|
||
3. `selectors.ts`:
|
||
- 提供状态查询和计算函数
|
||
- 供 UI 组件使用的状态订阅接口
|
||
- 重要: 使用 `export const xxxSelectors` 模式聚合所有 selectors
|
||
|
||
```typescript
|
||
// 典型的 selectors.ts 结构
|
||
import { ChatStoreState } from '../../initialState';
|
||
|
||
const currentTopics = (s: ChatStoreState): ChatTopic[] | undefined =>
|
||
s.topicMaps[s.activeId];
|
||
|
||
const currentActiveTopic = (s: ChatStoreState): ChatTopic | undefined => {
|
||
return currentTopics(s)?.find((topic) => topic.id === s.activeTopicId);
|
||
};
|
||
|
||
const getTopicById = (id: string) => (s: ChatStoreState): ChatTopic | undefined =>
|
||
currentTopics(s)?.find((topic) => topic.id === id);
|
||
|
||
// 核心模式:使用 xxxSelectors 聚合导出
|
||
export const topicSelectors = {
|
||
currentActiveTopic,
|
||
currentTopics,
|
||
getTopicById,
|
||
// ...其他 selectors
|
||
};
|
||
```
|
||
|
||
## 特殊 Slice 组织模式
|
||
|
||
### 复杂 Actions 的子目录结构 (aiChat Slice)
|
||
|
||
当 slice 的 actions 过于复杂时,可以拆分到子目录:
|
||
|
||
```
|
||
src/store/chat/slices/aiChat/
|
||
├── actions/
|
||
│ ├── generateAIChat.ts # AI 对话生成
|
||
│ ├── rag.ts # RAG 检索增强生成
|
||
│ ├── memory.ts # 对话记忆管理
|
||
│ └── index.ts # 聚合所有 actions
|
||
├── initialState.ts
|
||
├── selectors.ts
|
||
└── index.ts
|
||
```
|
||
|
||
参考:`src/store/chat/slices/aiChat/actions/`
|
||
|
||
### 工具类 Slice (builtinTool)
|
||
|
||
管理多种内置工具的状态:
|
||
|
||
```
|
||
src/store/chat/slices/builtinTool/
|
||
├── actions/
|
||
│ ├── dalle.ts # DALL-E 图像生成
|
||
│ ├── search.ts # 搜索功能
|
||
│ ├── localFile.ts # 本地文件操作
|
||
│ └── index.ts
|
||
├── initialState.ts
|
||
├── selectors.ts
|
||
└── index.ts
|
||
```
|
||
|
||
参考:`src/store/chat/slices/builtinTool/`
|
||
|
||
## 状态设计模式
|
||
|
||
### 1. Map 结构用于关联数据
|
||
```typescript
|
||
// 以 sessionId 为 key,管理多个会话的数据
|
||
topicMaps: Record<string, ChatTopic[]>
|
||
messagesMap: Record<string, ChatMessage[]>
|
||
```
|
||
|
||
### 2. 数组用于加载状态管理
|
||
```typescript
|
||
// 管理多个并发操作的加载状态
|
||
messageLoadingIds: string[]
|
||
topicLoadingIds: string[]
|
||
chatLoadingIds: string[]
|
||
```
|
||
|
||
### 3. 可选字段用于当前活动项
|
||
```typescript
|
||
// 当前激活的实体 ID
|
||
activeId: string
|
||
activeTopicId?: string
|
||
activeThreadId?: string
|
||
```
|
||
|
||
## Slice 集成到顶层 Store
|
||
|
||
### 1. 状态聚合
|
||
```typescript
|
||
// 在 initialState.ts 中
|
||
export type ChatStoreState = ChatTopicState &
|
||
ChatMessageState &
|
||
ChatAIChatState &
|
||
// ...其他 slice states
|
||
```
|
||
|
||
### 2. Action 接口聚合
|
||
```typescript
|
||
// 在 store.ts 中
|
||
export interface ChatStoreAction
|
||
extends ChatMessageAction,
|
||
ChatTopicAction,
|
||
ChatAIChatAction,
|
||
// ...其他 slice actions
|
||
```
|
||
|
||
### 3. Selector 统一导出
|
||
```typescript
|
||
// 在 selectors.ts 中 - 统一聚合 selectors
|
||
export { chatSelectors } from './slices/message/selectors';
|
||
export { topicSelectors } from './slices/topic/selectors';
|
||
export { aiChatSelectors } from './slices/aiChat/selectors';
|
||
|
||
// 每个 slice 的 selectors.ts 都使用 xxxSelectors 模式:
|
||
// export const chatSelectors = { ... }
|
||
// export const topicSelectors = { ... }
|
||
// export const aiChatSelectors = { ... }
|
||
```
|
||
|
||
## 最佳实践
|
||
|
||
1. Slice 划分原则:
|
||
- 按功能领域划分(message, topic, aiChat 等)
|
||
- 每个 slice 管理相关的状态和操作
|
||
- 避免 slice 之间的强耦合
|
||
|
||
2. 文件命名规范:
|
||
- 使用小驼峰命名 slice 目录
|
||
- 文件名使用一致的模式(action.ts, selectors.ts 等)
|
||
- 复杂 actions 时使用 actions/ 子目录
|
||
|
||
3. 状态结构设计:
|
||
- 扁平化的状态结构,避免深层嵌套
|
||
- 使用 Map 结构管理列表数据
|
||
- 分离加载状态和业务数据
|
||
|
||
4. 类型安全:
|
||
- 为每个 slice 定义清晰的 TypeScript 接口
|
||
- 使用 Zustand 的 StateCreator 确保类型一致性
|
||
- 在顶层聚合时保持类型安全
|
||
|
||
这种模块化的 slice 组织方式使得大型应用的状态管理变得清晰、可维护,并且易于扩展。
|