让我详细解释 chat.ts 中的计算属性工厂模式以及为什么不直接传入 store。
计算属性工厂模式解析
1. 什么是计算属性工厂
在 chat.ts 第 228-234 行:
typescript
// 计算属性工厂函数
createInstanceGetters: (moduleId: string) => ({
messages: computed(() => getMessages(moduleId)),
userInput: computed(() => getChatInstance(moduleId)?.userInput || ''),
isProcessing: computed(() => getChatInstance(moduleId)?.isProcessing || false),
showWelcome: computed(() => getChatInstance(moduleId)?.showWelcome ?? true),
config: computed(() => getChatInstance(moduleId)?.config || defaultConfig),
}),
这是一个工厂函数,它动态创建绑定到特定 moduleId 的计算属性。每次调用都会返回一个新的计算属性集合。
2. 为什么使用工厂模式
问题背景
typescript
// 如果直接这样做会有问题:
const chatStore = useChatStore();
const messages = computed(() => chatStore.getMessages("recitation"));
const userInput = computed(
() => chatStore.getChatInstance("recitation")?.userInput,
);
问题是:每个组件都需要重复写这些计算属性,并且需要硬编码 moduleId。
工厂模式的解决方案
typescript
// 工厂函数可以动态生成绑定到特定模块的计算属性
const recitationGetters = chatStore.createInstanceGetters("recitation");
const helpGetters = chatStore.createInstanceGetters("help");
3. useChatModule 的完整工作流程
typescript
export function useChatModule(moduleId: string, config?: Partial<ChatConfig>) {
const chatStore = useChatStore();
// 1. 初始化实例
chatStore.initChatInstance(moduleId, config);
// 2. 创建响应式getter(计算属性工厂的产物)
const getters = chatStore.createInstanceGetters(moduleId);
// 3. 返回混合对象:响应式数据 + 绑定方法
return {
...getters, // 包含 messages, userInput 等计算属性
// 方法绑定到当前模块
sendMessage: (content: string, context?: any) =>
chatStore.sendMessage(moduleId, content, context),
updateUserInput: (value: string) =>
chatStore.updateUserInput(moduleId, value),
// ... 其他方法
};
}
为什么不直接传入 Store
1. 模块隔离问题
如果直接传入 store:
vue
<!-- 这样会有问题 -->
<Chat :store="chatStore" moduleId="recitation" />
问题:
- 组件需要知道 moduleId
- 无法确保数据隔离
- 容易出现跨模块数据污染
2. 当前设计的优势
vue
<!-- 当前的设计 -->
<Chat :store="chat" />
其中 chat
是 useChatModule('recitation')
的返回值,包含:
typescript
{
// 响应式数据(已绑定到 'recitation' 模块)
messages: computed(() => getMessages('recitation')),
userInput: computed(() => getChatInstance('recitation')?.userInput),
// 绑定方法(已绑定到 'recitation' 模块)
sendMessage: (content) => chatStore.sendMessage('recitation', content),
updateUserInput: (value) => chatStore.updateUserInput('recitation', value),
}
3. 设计模式对比
方案A:直接传 Store(不推荐)
vue
<template>
<Chat :store="chatStore" :moduleId="'recitation'" />
</template>
<script>
// Chat.vue 内部需要处理 moduleId
const computedMessages = computed(() =>
props.store.getMessages(props.moduleId),
);
</script>
方案B:当前设计(推荐)
vue
<template>
<Chat :store="recitationChat" />
</template>
<script>
// 预绑定的模块实例
const recitationChat = useChatModule("recitation");
// Chat.vue 无需知道 moduleId
</script>
解决 Chat.vue 兼容性问题
当前 Chat.vue 的问题是没有正确处理计算属性。修复方案:
vue
import { ref, computed, unref } from 'vue'
vue
// 计算属性 - 根据传入的store 存在与否选择数据来源 const computedChatItems =
computed(() => { if (props.store && props.store.messages) { // 使用 unref
处理计算属性 const messages = unref(props.store.messages) if
(Array.isArray(messages)) { return messages.map((msg: any) => ({ key: msg.key,
loading: msg.loading || false, role: msg.role, content: msg.content, })) } }
return props.chatItems })
vue
const computedInputValue = computed(() => props.store ?
unref(props.store.userInput) : props.inputValue )
vue
const computedLoading = computed(() => props.store ?
unref(props.store.isProcessing) : props.loading )
vue
const computedShowWelcome = computed(() => { if (props.store) { const messages =
unref(props.store.messages) return props.showWelcome && (!messages ||
messages.length === 0) } return props.showWelcome && (!props.chatItems ||
props.chatItems.length === 0) })
总结
计算属性工厂模式的核心价值:
- 模块化:每个模块有独立的数据和方法
- 封装性:组件无需知道内部的 moduleId 和 store 结构
- 复用性:同一个 Chat 组件可以用于不同模块
- 类型安全:TypeScript 可以更好地推断类型
这种设计让 Chat 组件更加通用和易用,同时保持了良好的数据隔离。
- 本文链接:
- 版权声明:本博客所有文章除特别声明外,均默认采用 CC BY-NC-SA 4.0 许可协议。