关于 vue-composition
面向 Vue 应用开发者的 Vue 3 Composition API 参考技能,讲解响应式与组合式函数,并提供安全、错误处理、性能、无障碍和测试方面的指导。
主要功能
- 01响应式 API 与状态
- 02组合式函数模式
- 03组件与属性实践
- 04安全与错误处理
- 05性能和无障碍指导
- 06测试配置
适用场景
- 01构建可复用的组合式函数
- 02管理组件的响应式状态
- 03检查 Vue 应用的安全与性能
MCP 相关
该技能指向 `mcp__documentation__fetch_docs`,用于获取完整的 Vue 文档。
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ claude-dev-suite/ claude-dev-suite --skill vue-composition安装后在 Claude Code 中输入/vue-composition确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
advanced.mdquick-ref/composables.mdreactivity-cheatsheet.md详细介绍
Vue 3 Composition API
完整参考:请参阅 advanced.md,了解 WebSocket 组合式函数、provide/inject 插件模式、Socket.IO 集成和房间管理。
深入了解:使用
mcp__documentation__fetch_docs,并将技术设为:vue,获取全面的文档。
不要使用此技能的情况
以下情况请跳过此技能:
- 使用 Vue 2 Options API(请使用旧版 Vue 文档)
- 构建 React 应用(请使用
frontend-react) - 使用 Angular 框架(请使用
angular) - 使用 Svelte(请使用
svelte) - 处理仅在服务器端运行的逻辑(无需框架)
组件结构
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
interface Props {
title: string
count?: number
}
const props = defineProps<Props>()
const emit = defineEmits<{
update: [value: string]
}>()
const localState = ref('')
const doubled = computed(() => props.count * 2)
onMounted(() => {
console.log('Component mounted')
})
</script>
<template>
<div>
<h1>{{ title }}</h1>
<p>{{ doubled }}</p>
</div>
</template>
响应式系统
| API | 用途 |
|---|---|
ref() | 基本类型的响应式值 |
reactive() | 响应式对象 |
computed() | 派生状态 |
watch() | 监听响应式数据源 |
watchEffect() | 自动追踪依赖 |
组合式函数模式
// useCounter.ts
export function useCounter(initial = 0) {
const count = ref(initial)
const increment = () => count.value++
const decrement = () => count.value--
return { count, increment, decrement }
}
核心概念
- 推荐使用
<script setup>语法 - 基本类型使用
ref,对象使用reactive - 使用
v-model实现双向绑定 - 使用插槽分发内容
反模式
| 反模式 | 不良原因 | 正确做法 |
|---|---|---|
对基本类型使用 reactive() | 解构时会失去响应性 | 基本类型使用 ref() |
| 直接修改 props | 破坏单向数据流 | 触发事件,或使用 v-model |
未经清理就使用 v-html | 存在 XSS 漏洞 | 渲染前使用 DOMPurify |
| 使用未做记忆化的大型 computed | 每次渲染都会重新计算 | 拆分为更小的 computed |
未在 onUnmounted 中清理 | 会造成内存泄漏 | 清除计时器,取消订阅 |
在 computed 足以满足需求时仍使用 watch | 增加不必要的复杂性 | 派生状态使用 computed |
快速故障排查
| 问题 | 可能原因 | 解决方案 |
|---|---|---|
| Computed 未更新 | 忘记为 ref 使用 .value | 使用 .value 访问 ref |
| 模板不是响应式的 | 使用了 let 而不是 ref | 转换为 ref() 或 reactive() |
| Props 修改警告 | 直接修改了 props | 克隆 props 或触发更新事件 |
| 组件未重新渲染 | 对基本类型使用了 reactive | 基本类型使用 ref() |
| 内存泄漏 | 忘记清理 | 在 onUnmounted 中添加清理逻辑 |
v-model 不起作用 | 事件名称错误 | 使用 update:modelValue 事件 |
生产环境就绪
安全最佳实践
<script setup lang="ts">
// NEVER use v-html with user input without sanitization
import DOMPurify from 'dompurify'
const props = defineProps<{ userContent: string }>()
const sanitizedContent = computed(() => DOMPurify.sanitize(props.userContent))
</script>
<template>
<div v-html="userContent" />
<div v-html="sanitizedContent" />
</template>
// Validate external URLs
const isValidUrl = (url: string): boolean => {
try {
const parsed = new URL(url)
return ['http:', 'https:'].includes(parsed.protocol)
} catch {
return false
}
}
// Never expose secrets in client code
// Use runtime config or server routes instead
const config = useRuntimeConfig()
// config.public.* is safe for client
// config.* (without public) stays server-side
错误处理
<script setup lang="ts">
import { onErrorCaptured } from 'vue'
// Component-level error boundary
onErrorCaptured((error, instance, info) => {
// Log to error tracking service
logError(error, { component: instance?.$options.name, info })
// Return false to prevent error propagation
return false
})
</script>
// Global error handler (main.ts)
const app = createApp(App)
app.config.errorHandler = (error, instance, info) => {
console.error('Global error:', error)
// Send to error tracking (Sentry, etc.)
captureException(error, { extra: { info } })
}
app.config.warnHandler = (msg, instance, trace) => {
// Log warnings in development
if (import.meta.env.DEV) console.warn(msg, trace)
}
性能优化
<script setup lang="ts">
import { defineAsyncComponent, shallowRef } from 'vue'
// Lazy load heavy components
const HeavyChart = defineAsyncComponent({
loader: () => import('./HeavyChart.vue'),
loadingComponent: LoadingSpinner,
delay: 200,
errorComponent: ErrorDisplay,
})
// Use shallowRef for large objects that don't need deep reactivity
const largeDataset = shallowRef<DataItem[]>([])
// Computed with getter/setter for derived state
const filteredItems = computed(() =>
items.value.filter(item => item.active)
)
</script>
<template>
<header v-once>
<h1>{{ appTitle }}</h1>
</header>
<div v-for="item in list" :key="item.id" v-memo="[item.id, item.updated]">
<ExpensiveComponent :data="item" />
</div>
<VirtualList :items="largeDataset" :item-height="50" />
</template>
无障碍(a11y)
<template>
<button @click="handleClick">Submit</button>
<div role="alert" aria-live="polite" v-if="error">
{{ error }}
</div>
<dialog ref="dialogRef" @vue:mounted="dialogRef?.focus()">
<h2 id="dialog-title">Confirm Action</h2>
<div aria-labelledby="dialog-title">...</div>
</dialog>
</template>
测试设置
// Component testing with Vue Test Utils
import { mount } from '@vue/test-utils'
import { describe, it, expect, vi } from 'vitest'
describe('UserForm', () => {
it('emits submit with form data', async () => {
const wrapper = mount(UserForm)
await wrapper.find('input[name="email"]').setValue('[email protected]')
await wrapper.find('form').trigger('submit')
expect(wrapper.emitted('submit')).toBeTruthy()
expect(wrapper.emitted('submit')[0]).toEqual([{ email: '[email protected]' }])
})
})
监控指标
| 指标 | 警报阈值 |
|---|---|
| 最大内容绘制(LCP) | > 2.5s |
| 首次输入延迟(FID) | > 100ms |
| 累积布局偏移(CLS) | > 0.1 |
| JavaScript 包大小 | > 200KB(gzip 压缩后) |
| 组件渲染时间 | > 16ms |
构建优化
// vite.config.ts
export default defineConfig({
build: {
rollupOptions: {
output: {
manualChunks: {
vue: ['vue', 'vue-router', 'pinia'],
ui: ['@headlessui/vue', '@vueuse/core'],
},
},
},
sourcemap: true,
},
})
检查清单
- 已配置全局错误处理程序
- 客户端状态中不含敏感数据
- 对 v-html 内容使用 DOMPurify
- 使用异步组件进行代码拆分
- 对大型非响应式数据使用 shallowRef
- 对开销较大的列表渲染使用 v-memo
- 对长列表使用虚拟滚动
- 使用语义化 HTML 和 ARIA 标签
- 已监控核心 Web 指标
- 已优化包大小
- 已集成错误报告服务
参考文档
深入了解:使用
mcp__documentation__fetch_docs,并将技术设为:vue,获取全面的文档。
评论
支持 Markdown