
Vue 3 组件开发完全指南基于 claude-skills 的 Vue Expert 参考手册【免费下载链接】claude-skills67 Specialized Skills for Full-Stack Developers. Transform Claude Code into your expert pair programmer.项目地址: https://gitcode.com/GitHub_Trending/claud/claude-skills本篇技术指南以开源仓库 claude-skills 中 Vue Expert 技能包 的组件参考文档skills/vue-expert/references/components.md为骨架系统讲解 Vue 3 组件开发中的 Props 类型化、事件发射、v-model 双向绑定、插槽、Provide/Inject 依赖注入、Teleport、动态与异步组件等核心机制。读完你将掌握一套可直接用于生产项目的 Vue 3 TypeScript 组件开发模式并了解这些模式在vue-expert技能中的约束与调用方式。在 claude-skills 仓库中vue-expert是一个面向 Vue 3 Composition API 的专家技能其 SKILL.md 明确要求使用 Composition API而非 Options API使用script setup语法使用类型安全的 TypeScript Props而 components.md 正是该技能在组件主题下的深度参考与 typescript.md、composition-api.md 共同构成组件的完整知识体系。一、Props 的类型化声明defineProps 与 withDefaults在 Vue 3 的script setup中Props 的声明分为两条路径类型化声明TypeScript与运行时声明纯 JavaScript。类型化声明推荐script setup langts // Simple props interface Props { title: string count?: number items: string[] } const props definePropsProps() // Props with defaults const propsWithDefaults withDefaults(definePropsProps(), { count: 0, items: () [] }) // Access props console.log(props.title) console.log(props.count) /script template div h1{{ title }}/h1 pCount: {{ count }}/p /div /template这段代码的要点在于definePropsProps()通过泛型把 Props 的编译期类型约束交给 TypeScript模板中可以直接使用未加前缀的title、countVue 会自动解包脚本中则通过props.title、props.count访问可选属性用?标记如count?: number当存在可选属性且需要默认值时必须配合withDefaults()使用且对象/数组类型的默认值必须用工厂函数返回items: () []否则多个组件实例会共享同一引用。在 typescript.md 中可以看到这套模式的进阶形式支持联合类型status: success | error | warning、复杂对象user: User、callback: (id: number) void以及Recordstring, unknown这类宽松配置对象。事实上claude-skills 的 SKILL.md 快速示例Quick Example就使用了defineProps{ initialCount?: number }()这种行内接口写法印证了这是该技能的标准偏好。运行时声明无 TypeScript 场景script setup langts import type { PropType } from vue // Runtime props (without TypeScript) const runtimeProps defineProps({ title: { type: String, required: true }, count: { type: Number, default: 0, validator: (value: number) value 0 }, items: { type: Array as PropTypestring[], default: () [] } }) /script运行时声明的两个关键补充validator校验器count的校验器value 0在每次父组件传值变更时执行不通过时 Vue 会抛出警告——这是原文档代码中含有的校验逻辑务必保留PropTypeT泛型断言原生Array、Object类型无法表达元素类型必须用as PropTypestring[]补充。二、事件发射defineEmits 的类型安全与运行时校验类型化 Emitsscript setup langts // TypeScript emits interface Emits { (e: update, value: string): void (e: delete, id: number): void (e: submit, payload: { name: string; email: string }): void } const emit defineEmitsEmits() // Emit events function handleUpdate() { emit(update, new value) } function handleDelete(id: number) { emit(delete, id) } function handleSubmit() { emit(submit, { name: John, email: johnexample.com }) } /script template button clickhandleUpdateUpdate/button button clickhandleDelete(123)Delete/button /templatedefineEmitsEmits()使用函数调用签名接口来描述事件每个事件名是一个(e: 事件名, payload) void重载。这样做的直接收益在 typescript.md 中有明确注释emit(update, 123)会因为number不可赋值给string而在编译期直接报错。此外typescript.md 还给出了等价的元组语法type EmitsType { update: [value: string] delete: [id: number] submit: [payload: { name: string; email: string }] } const emit2 defineEmitsEmitsType()两种语法等效元组形式对事件参数较多的情况更易读。运行时 Emits 校验script setup langts // Runtime emits with validation const runtimeEmit defineEmits({ update: (value: string) { return value.length 0 }, delete: (id: number) { return id 0 } }) /script运行时形式的defineEmits接收一个以事件名为键的对象值为返回布尔值的校验函数。当校验返回false时Vue 会在开发环境提示警告事件仍会发射。这层校验适合在不依赖 TypeScript 或需要对外部输入做防御性检查时使用。三、v-model 双向绑定从单值到多值绑定Vue 3 的v-model本质是modelValue属性 update:modelValue事件的语法糖并且原生支持多 v-modelVue 3.0和自定义参数名。单个与多个 v-model!-- Parent Component -- script setup langts import { ref } from vue import CustomInput from ./CustomInput.vue const searchQuery ref() const filters ref({ category: , price: 0 }) /script template !-- Single v-model -- CustomInput v-modelsearchQuery / !-- Multiple v-models -- FilterPanel v-model:categoryfilters.category v-model:pricefilters.price / /template !-- CustomInput.vue -- script setup langts interface Props { modelValue: string } interface Emits { (e: update:modelValue, value: string): void } const props definePropsProps() const emit defineEmitsEmits() function handleInput(event: Event) { const target event.target as HTMLInputElement emit(update:modelValue, target.value) } /script template input :valuemodelValue inputhandleInput / /template关键实现细节子组件中v-model:categoryfilters.category对应 Props 名category与事件名update:categoryFilterPanel.vue中对select使用:valuecategorychange对input typenumber使用:valuepriceinput并在发射时显式做类型转换Number(($event.target as HTMLInputElement).value)——避免字符串与数字类型漂移!-- FilterPanel.vue with multiple v-models -- script setup langts interface Props { category: string price: number } interface Emits { (e: update:category, value: string): void (e: update:price, value: number): void } const props definePropsProps() const emit defineEmitsEmits() /script template select :valuecategory changeemit(update:category, ($event.target as HTMLSelectElement).value) option valuebooksBooks/option option valueelectronicsElectronics/option /select input typenumber :valueprice inputemit(update:price, Number(($event.target as HTMLInputElement).value)) / /template注意不要直接修改 props。这是vue-expert技能在 SKILL.md 的 MUST NOT DO 中明确禁止的行为Mutate props directly因为单向数据流下 props 的变更会被父组件覆盖正确的做法永远是子组件通过 emit 请求父组件更新。四、插槽内容分发、命名插槽与作用域插槽基础插槽模式!-- Parent Component -- template Card template #header h2Card Title/h2 /template template #default pMain content goes here/p /template template #footer{ close } button clickcloseClose/button /template /Card /template !-- Card.vue -- script setup langts import { useSlots } from vue const slots useSlots() // Check if slot exists const hasHeader !!slots.header const hasFooter !!slots.footer function close() { console.log(Closing card) } /script template div classcard div v-ifhasHeader classcard-header slot nameheader/slot /div div classcard-body slot/slot !-- Default slot -- /div div v-ifhasFooter classcard-footer slot namefooter :closeclose/slot !-- Scoped slot -- /div /div /template这段代码同时演示了三个概念命名插槽slot nameheader配合父组件的template #header实现模板级的内容分发条件插槽渲染useSlots()返回插槽对象!!slots.header判断父组件是否传入了该插槽从而用v-if避免渲染空的 header/footer 容器作用域插槽slot namefooter :closeclose把子组件的close函数作为插槽属性向下暴露父组件通过template #footer{ close }解构使用——数据流向由子传父完成。泛型作用域插槽列表组件实战!-- List Component with Scoped Slot -- script setup langts genericT interface Props { items: T[] } const props definePropsProps() /script template div classlist div v-for(item, index) in items :keyindex slot :itemitem :indexindex/slot /div /div /template !-- Usage -- template List :itemsusers template #default{ item, index } div{{ index }}: {{ item.name }}/div /template /List /template这里用到了script setup langts genericT泛型语法组件List的类型参数T使得它成为完全可复用的列表组件而#default{ item, index }则把列表项的渲染完全交给父组件定制。该语法在 typescript.md 的泛型组件一节有更完整的约束示例genericT extends { id: number }。五、Provide/Inject跨层依赖注入告别 Props 钻透当组件嵌套层级很深3 层以上时逐层传递 props 会造成Prop Drilling。Vue 3 提供了provide/inject在祖先与任意后代组件间直接共享数据。类型安全的 InjectionKey 模式!-- Parent Component (Provider) -- script setup langts import { provide, ref, readonly, InjectionKey } from vue // Type-safe injection key interface UserData { name: string email: string } export const userKey Symbol() as InjectionKeyUserData const user refUserData({ name: John Doe, email: johnexample.com }) function updateUser(newUser: UserData) { user.value newUser } // Provide data provide(userKey, readonly(user.value)) provide(updateUser, updateUser) /script !-- Child Component (Injector) -- script setup langts import { inject } from vue import { userKey } from ./Parent.vue // Inject with type safety const user inject(userKey) const updateUser inject(user: UserData) void(updateUser) // Inject with default value const theme inject(theme, light) function handleUpdate() { if (updateUser) { updateUser({ name: Jane, email: janeexample.com }) } } /script template div pUser: {{ user?.name }}/p pTheme: {{ theme }}/p button clickhandleUpdateUpdate User/button /div /template实践中必须掌握的四个细节Symbol() as InjectionKeyT用Symbol保证 key 全局唯一配合InjectionKey让inject自动推断返回类型避免魔法字符串拼写错误readonly()包裹响应式值provide(userKey, readonly(user.value))让下游只能读不能改把唯一修改入口收敛在 Provider 内部如updateUser这是单向数据流在跨层场景的延续inject 泛型标注对字符串 key 的注入inject(user: UserData) void(updateUser)手动声明返回类型调用前做空值检查if (updateUser)防止未注入时报错默认值inject(theme, light)在没有 Provider 时返回默认值适合主题色这类全局但可选的配置。typescript.md 还补充了两种更严格的注入策略用ref()包裹的完整 context 对象{ user, updateUser }以及未注入即抛错的防御写法if (!requiredContext) throw new Error(...)适合关键上下文不允许缺失的场景。六、Teleport把 DOM 渲染到组件层级之外弹窗、通知、Toast 等组件需要覆盖全屏但如果渲染在深层嵌套的组件树内就可能被父级的overflow: hidden、transform、z-index上下文截断。Teleport可以把内容传送到任意指定 DOM 节点。script setup langts import { ref } from vue const showModal ref(false) const isMobile ref(false) /script template button clickshowModal trueShow Modal/button !-- Teleport to body -- Teleport tobody div v-ifshowModal classmodal div classmodal-content h2Modal Title/h2 pModal content/p button clickshowModal falseClose/button /div /div /Teleport !-- Teleport to specific element -- Teleport to#modal-container div classnotificationNotification message/div /Teleport !-- Conditional teleport -- Teleport tobody :disabled!isMobile divOnly teleported on mobile/div /Teleport /template style scoped .modal { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.5); display: flex; align-items: center; justify-content: center; } .modal-content { background: white; padding: 2rem; border-radius: 8px; } /style三种典型用法tobody传送到body末尾模态框样式用position: fixed覆盖全屏完全避开祖先组件的样式作用域限制to#modal-container传送到页面中预留的挂载节点适合通知/浮层统一管理:disabled!isMobile条件传送——移动端传送到 body 保证全屏体验桌面端保留在原位置渲染。注意传送出的内容依然保持 Vue 组件的响应式与事件逻辑scoped样式依然生效。七、动态组件与 KeepAlive按需切换组件实例script setup langts import { ref, shallowRef, Component } from vue import HomeView from ./HomeView.vue import AboutView from ./AboutView.vue import ContactView from ./ContactView.vue // Use shallowRef for component references (performance) const currentView shallowRefComponent(HomeView) const components { home: HomeView, about: AboutView, contact: ContactView } function switchView(view: keyof typeof components) { currentView.value components[view] } /script template button clickswitchView(home)Home/button button clickswitchView(about)About/button button clickswitchView(contact)Contact/button !-- Dynamic component with KeepAlive -- KeepAlive component :iscurrentView / /KeepAlive /template两个值得注意的实现细节shallowRef而非ref组件引用本身不会被深层追踪用shallowRef避免对组件实例进行不必要的响应式深度转换是页面切换这类高频场景的性能优化点原文档注释明确标注performanceKeepAlive包裹component :is切换视图时默认会销毁旧组件实例KeepAlive会缓存被切换走的组件实例避免重新挂载带来的状态丢失与开销。配合keyof typeof componentsswitchView的入参在编译期就限定为home | about | contact。八、异步组件与 Suspense按需加载与加载态管理大型应用的体积优化从按需加载开始。defineAsyncComponent让组件在首次渲染时才请求其代码块Code Splitting配合Suspense提供占位 UI。script setup langts import { defineAsyncComponent } from vue // Lazy load component const HeavyComponent defineAsyncComponent(() import(./HeavyComponent.vue) ) // With loading and error states const AdminPanel defineAsyncComponent({ loader: () import(./AdminPanel.vue), loadingComponent: () import(./LoadingSpinner.vue), errorComponent: () import(./ErrorDisplay.vue), delay: 200, // Delay before showing loading component timeout: 3000 // Timeout before showing error }) /script template Suspense template #default HeavyComponent / /template template #fallback divLoading.../div /template /Suspense /template参数语义说明loader返回import()的动态导入函数loadingComponent与delay: 200快速加载时小于 200ms直接渲染目标组件、不闪加载态超过 200ms 才展示 Spinner避免闪烁errorComponent与timeout: 3000超过 3 秒仍未加载完成则切换到错误组件Suspense的#default/#fallback当默认插槽中的异步组件未就绪时渲染 fallback这里的fallback优先级高于delay通常作为最外层的兜底占位。九、快速参考表十种组件模式速查原文档以一张速查表收束全篇这里完整保留并补充典型使用场景PatternUse CasedefinePropsT()Type-safe props with TypeScriptwithDefaults()Props with default valuesdefineEmitsT()Type-safe event emittersv-modelTwo-way data bindingslotContent distributionScoped slotsPass data from child to parentprovide/injectDependency injection (avoid prop drilling)TeleportRender DOM outside component hierarchycomponent :isDynamic component switchingdefineAsyncComponent()Lazy load components这十种模式恰好覆盖了组件通信Props / Emits / v-model、内容分发Slot / Scoped Slot、跨层共享provide/inject、DOM 位置控制Teleport与性能策略动态/异步组件五大维度是日常 Vue 3 开发中最高频的能力组合。十、在 claude-skills 技能体系中的定位与调用方式何时加载这份参考vue-expert技能通过 SKILL.md 中的Reference Guide按需加载参考文档components.md的触发条件是Props、emits、slots、provide/inject相关任务TopicReferenceLoad WhenComponentsreferences/components.mdProps, emits, slots, provide/inject也就是说当你在 Claude Code 中请求为现有组件补充类型安全的 props 与事件或重构深层级联的组件为 provide/inject时该技能会自动加载这份参考作为实现依据。必须遵守的编码约束本文所有示例均遵循vue-expert在 SKILL.md 中声明的约束ConstraintsMUST DO使用 Composition API禁用 Options API使用script setup语法使用类型安全的 TypeScript Props用ref()处理基本类型、reactive()处理对象派生状态用computed()正确使用生命周期钩子组合式函数中做好清理。MUST NOT DO使用 Options APIdata/methods/computed对象写法混用 Composition API 与 Options API直接修改 propscomputed能解决时使用watch忘记清理 watcher 与副作用在onMounted之前访问 DOM。与相邻参考文档的协同类型体系defineProps/defineEmits/provideinject的完整类型进阶联合类型、泛型组件、InjectionKey、模板 ref、可写 computed见 typescript.md响应式基础ref与reactive的选择、watch与watchEffect、生命周期钩子、composables 组合式函数模式见 composition-api.md全局状态当组件间共享状态超出 provide/inject 的适用边界时应切换到 Pinia 的 Setup Store 风格refcomputeddefineStore相关实践见 state-management.md其中storeToRefs()保活响应式解构、setActivePinia()隔离测试等模式与本文组件 API 高度互补。在 SKILLS_GUIDE.md 的技能决策树中Vue 3 Composition API patterns 类请求会直接路由到vue-expert技能在 README.md 定义的多技能工作流里Vue Expert 常与 TypeScript Pro、API Designer、DevOps Engineer 等技能组合完成前端全链路任务。掌握本文的组件通信与渲染机制是你用好整套 claude-skills 前端能力栈的第一步。【免费下载链接】claude-skills67 Specialized Skills for Full-Stack Developers. Transform Claude Code into your expert pair programmer.项目地址: https://gitcode.com/GitHub_Trending/claud/claude-skills创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考