|
| 1 | +# messageBlock.ts 使用指南 |
| 2 | + |
| 3 | +该文件定义了用于管理应用程序中所有 `MessageBlock` 实体的 Redux Slice。它使用 Redux Toolkit 的 `createSlice` 和 `createEntityAdapter` 来高效地处理规范化的状态,并提供了一系列 actions 和 selectors 用于与消息块数据交互。 |
| 4 | + |
| 5 | +## 核心目标 |
| 6 | + |
| 7 | +- **状态管理**: 集中管理所有 `MessageBlock` 的状态。`MessageBlock` 代表消息中的不同内容单元(如文本、代码、图片、引用等)。 |
| 8 | +- **规范化**: 使用 `createEntityAdapter` 将 `MessageBlock` 数据存储在规范化的结构中(`{ ids: [], entities: {} }`),这有助于提高性能和简化更新逻辑。 |
| 9 | +- **可预测性**: 提供明确的 actions 来修改状态,并通过 selectors 安全地访问状态。 |
| 10 | + |
| 11 | +## 关键概念 |
| 12 | + |
| 13 | +- **Slice (`createSlice`)**: Redux Toolkit 的核心 API,用于创建包含 reducer 逻辑、action creators 和初始状态的 Redux 模块。 |
| 14 | +- **Entity Adapter (`createEntityAdapter`)**: Redux Toolkit 提供的工具,用于简化对规范化数据的 CRUD(创建、读取、更新、删除)操作。它会自动生成 reducer 函数和 selectors。 |
| 15 | +- **Selectors**: 用于从 Redux store 中派生和计算数据的函数。Selectors 可以被记忆化(memoized),以提高性能。 |
| 16 | + |
| 17 | +## State 结构 |
| 18 | + |
| 19 | +`messageBlocks` slice 的状态结构由 `createEntityAdapter` 定义,大致如下: |
| 20 | + |
| 21 | +```typescript |
| 22 | +{ |
| 23 | + ids: string[]; // 存储所有 MessageBlock ID 的有序列表 |
| 24 | + entities: { [id: string]: MessageBlock }; // 按 ID 存储 MessageBlock 对象的字典 |
| 25 | + loadingState: 'idle' | 'loading' | 'succeeded' | 'failed'; // (可选) 其他状态,如加载状态 |
| 26 | + error: string | null; // (可选) 错误信息 |
| 27 | +} |
| 28 | +``` |
| 29 | + |
| 30 | +## Actions |
| 31 | + |
| 32 | +该 slice 导出以下 actions (由 `createSlice` 和 `createEntityAdapter` 自动生成或自定义): |
| 33 | + |
| 34 | +- **`upsertOneBlock(payload: MessageBlock)`**: |
| 35 | + |
| 36 | + - 添加一个新的 `MessageBlock` 或更新一个已存在的 `MessageBlock`。如果 payload 中的 `id` 已存在,则执行更新;否则执行插入。 |
| 37 | + |
| 38 | +- **`upsertManyBlocks(payload: MessageBlock[])`**: |
| 39 | + |
| 40 | + - 添加或更新多个 `MessageBlock`。常用于批量加载数据(例如,加载一个 Topic 的所有消息块)。 |
| 41 | + |
| 42 | +- **`removeOneBlock(payload: string)`**: |
| 43 | + |
| 44 | + - 根据提供的 `id` (payload) 移除单个 `MessageBlock`。 |
| 45 | + |
| 46 | +- **`removeManyBlocks(payload: string[])`**: |
| 47 | + |
| 48 | + - 根据提供的 `id` 数组 (payload) 移除多个 `MessageBlock`。常用于删除消息或清空 Topic 时清理相关的块。 |
| 49 | + |
| 50 | +- **`removeAllBlocks()`**: |
| 51 | + |
| 52 | + - 移除 state 中的所有 `MessageBlock` 实体。 |
| 53 | + |
| 54 | +- **`updateOneBlock(payload: { id: string; changes: Partial<MessageBlock> })`**: |
| 55 | + |
| 56 | + - 更新一个已存在的 `MessageBlock`。`payload` 需要包含块的 `id` 和一个包含要更改的字段的 `changes` 对象。 |
| 57 | + |
| 58 | +- **`setMessageBlocksLoading(payload: 'idle' | 'loading')`**: |
| 59 | + |
| 60 | + - (自定义) 设置 `loadingState` 属性。 |
| 61 | + |
| 62 | +- **`setMessageBlocksError(payload: string)`**: |
| 63 | + - (自定义) 设置 `loadingState` 为 `'failed'` 并记录错误信息。 |
| 64 | + |
| 65 | +**使用示例 (在 Thunk 或其他 Dispatch 的地方):** |
| 66 | + |
| 67 | +```typescript |
| 68 | +import { upsertOneBlock, removeManyBlocks, updateOneBlock } from './messageBlock' |
| 69 | +import store from './store' // 假设这是你的 Redux store 实例 |
| 70 | +
|
| 71 | +// 添加或更新一个块 |
| 72 | +const newBlock: MessageBlock = { |
| 73 | + /* ... block data ... */ |
| 74 | +} |
| 75 | +store.dispatch(upsertOneBlock(newBlock)) |
| 76 | + |
| 77 | +// 更新一个块的内容 |
| 78 | +store.dispatch(updateOneBlock({ id: blockId, changes: { content: 'New content' } })) |
| 79 | + |
| 80 | +// 删除多个块 |
| 81 | +const blockIdsToRemove = ['id1', 'id2'] |
| 82 | +store.dispatch(removeManyBlocks(blockIdsToRemove)) |
| 83 | +``` |
| 84 | + |
| 85 | +## Selectors |
| 86 | + |
| 87 | +该 slice 导出由 `createEntityAdapter` 生成的基础 selectors,并通过 `messageBlocksSelectors` 对象访问: |
| 88 | + |
| 89 | +- **`messageBlocksSelectors.selectIds(state: RootState): string[]`**: 返回包含所有块 ID 的数组。 |
| 90 | +- **`messageBlocksSelectors.selectEntities(state: RootState): { [id: string]: MessageBlock }`**: 返回块 ID 到块对象的映射字典。 |
| 91 | +- **`messageBlocksSelectors.selectAll(state: RootState): MessageBlock[]`**: 返回包含所有块对象的数组。 |
| 92 | +- **`messageBlocksSelectors.selectTotal(state: RootState): number`**: 返回块的总数。 |
| 93 | +- **`messageBlocksSelectors.selectById(state: RootState, id: string): MessageBlock | undefined`**: 根据 ID 返回单个块对象,如果找不到则返回 `undefined`。 |
| 94 | + |
| 95 | +**此外,还提供了一个自定义的、记忆化的 selector:** |
| 96 | + |
| 97 | +- **`selectFormattedCitationsByBlockId(state: RootState, blockId: string | undefined): Citation[]`**: |
| 98 | + - 接收一个 `blockId`。 |
| 99 | + - 如果该 ID 对应的块是 `CITATION` 类型,则提取并格式化其包含的引用信息(来自网页搜索、知识库等),进行去重和重新编号,最后返回一个 `Citation[]` 数组,用于在 UI 中显示。 |
| 100 | + - 如果块不存在或类型不匹配,返回空数组 `[]`。 |
| 101 | + - 这个 selector 封装了处理不同引用来源(Gemini, OpenAI, OpenRouter, Zhipu 等)的复杂逻辑。 |
| 102 | + |
| 103 | +**使用示例 (在 React 组件或 `useSelector` 中):** |
| 104 | + |
| 105 | +```typescript |
| 106 | +import { useSelector } from 'react-redux' |
| 107 | +import { messageBlocksSelectors, selectFormattedCitationsByBlockId } from './messageBlock' |
| 108 | +import type { RootState } from './store' |
| 109 | + |
| 110 | +// 获取所有块 |
| 111 | +const allBlocks = useSelector(messageBlocksSelectors.selectAll) |
| 112 | + |
| 113 | +// 获取特定 ID 的块 |
| 114 | +const specificBlock = useSelector((state: RootState) => messageBlocksSelectors.selectById(state, someBlockId)) |
| 115 | + |
| 116 | +// 获取特定引用块格式化后的引用列表 |
| 117 | +const formattedCitations = useSelector((state: RootState) => selectFormattedCitationsByBlockId(state, citationBlockId)) |
| 118 | + |
| 119 | +// 在组件中使用引用数据 |
| 120 | +// {formattedCitations.map(citation => ...)} |
| 121 | +``` |
| 122 | + |
| 123 | +## 集成 |
| 124 | + |
| 125 | +`messageBlock.ts` slice 通常与 `messageThunk.ts` 中的 Thunks 紧密协作。Thunks 负责处理异步逻辑(如 API 调用、数据库操作),并在需要时 dispatch `messageBlock` slice 的 actions 来更新状态。例如,当 `messageThunk` 接收到流式响应时,它会 dispatch `upsertOneBlock` 或 `updateOneBlock` 来实时更新对应的 `MessageBlock`。同样,删除消息的 Thunk 会 dispatch `removeManyBlocks`。 |
| 126 | + |
| 127 | +理解 `messageBlock.ts` 的职责是管理**状态本身**,而 `messageThunk.ts` 负责**触发状态变更**的异步流程,这对于维护清晰的应用架构至关重要。 |
0 commit comments