banner
Hi my new friend!

vue里pinia计算属性工厂模式解析

Scroll down

让我详细解释 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" />

其中 chatuseChatModule('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) })

总结

计算属性工厂模式的核心价值:

  1. 模块化:每个模块有独立的数据和方法
  2. 封装性:组件无需知道内部的 moduleId 和 store 结构
  3. 复用性:同一个 Chat 组件可以用于不同模块
  4. 类型安全:TypeScript 可以更好地推断类型

这种设计让 Chat 组件更加通用和易用,同时保持了良好的数据隔离。

  • 本文作者:async
  • 本文链接:
  • 版权声明:本博客所有文章除特别声明外,均默认采用 CC BY-NC-SA 4.0 许可协议。
其他文章
cover
回溯
  • 25-06-06
  • 03:14
  • 算法
cover
vitepress配置
  • 25-05-21
  • 04:20
  • 前端技术