# Vue 代码范式 仅在项目版本和既有约定支持时使用以下范式。复制前替换领域名称、错误处理和组件库 API。 ## 类型安全组件契约 ```vue ``` 要点: - 将 props 和 emits 作为公共契约。 - 使用稳定业务 id 发出事件,不把完整可变内部对象泄露出去。 - 使用原生 button 获得键盘和语义能力。 - 若项目 Vue 版本支持且团队采用响应式 props 解构,可用等价的解构默认值写法。 ## 带竞态保护的 composable ```ts import { readonly, ref, toValue, watch, type MaybeRefOrGetter } from 'vue' interface User { id: string name: string } interface UserService { getUser(userId: string, signal: AbortSignal): Promise } export function useUser( userId: MaybeRefOrGetter, service: UserService, ) { const data = ref(null) const error = ref(null) const isLoading = ref(false) watch( () => toValue(userId), async (currentUserId, _previousUserId, onCleanup) => { const controller = new AbortController() onCleanup(() => controller.abort()) isLoading.value = true error.value = null try { data.value = await service.getUser(currentUserId, controller.signal) } catch (cause: unknown) { if (!controller.signal.aborted) { error.value = cause instanceof Error ? cause : new Error('Unknown user loading error') } } finally { if (!controller.signal.aborted) { isLoading.value = false } } }, { immediate: true }, ) return { data: readonly(data), error: readonly(error), isLoading: readonly(isLoading), } } ``` 要点: - 通过依赖参数隔离网络边界,便于测试。 - 对参数变化取消旧请求,避免过期结果覆盖新状态。 - 只暴露 readonly 状态。 - 实际项目应使用已有错误归一化,不重复定义通用错误层。 ## 最小 Pinia Store ```ts import { computed, ref } from 'vue' import { defineStore } from 'pinia' export const useCartStore = defineStore('cart', () => { const itemIds = ref([]) const itemCount = computed(() => itemIds.value.length) function addItem(itemId: string): void { if (!itemIds.value.includes(itemId)) { itemIds.value.push(itemId) } } function clear(): void { itemIds.value = [] } return { itemIds, itemCount, addItem, clear, } }) ``` 要点: - 只保存不可推导的最小 state。 - 用 computed 表达 getter。 - 通过 action 表达状态变化。 - 真实 store 若执行请求,需补齐 loading、error、并发与恢复策略。 ## 行为导向组件测试 ```ts import { mount } from '@vue/test-utils' import { describe, expect, it } from 'vitest' import UserSelectButton from './UserSelectButton.vue' describe('UserSelectButton', () => { it('emits the selected user id when activated', async () => { const wrapper = mount(UserSelectButton, { props: { user: { id: 'user-42', displayName: 'Ada' }, }, }) await wrapper.get('button').trigger('click') expect(wrapper.emitted('select')).toEqual([['user-42']]) }) }) ``` 要点: - 通过用户可操作元素交互。 - 验证公开事件和业务结果,不访问组件内部 ref。 - 对无障碍名称、loading、disabled 和 error 等关键状态按变更补充断言。