Vue3——第 7 篇:组件基础——从注册到使用的完整流程

发布时间:2026/10/21 20:34:46
Vue3——第 7 篇:组件基础——从注册到使用的完整流程 本篇定位组件化是 Vue 最核心的开发范式也是构建大型应用的基础。本篇将带你从零开始理解 Vue 组件的本质、注册方式、Props 数据传递和类型校验。读完本篇你将能够独立创建和复用 Vue 组件并理解组件间数据流动的基本原则。一、什么是组件1.1 组件的本质Vue 组件是可复用的 Vue 实例它拥有独立的模板、逻辑和样式。从本质上讲组件就是一个带有特定行为和样式的 UI 片段。可以这样理解一个 Vue 应用就像一棵组件树根组件App.vue是树根其他组件是其枝叶每个组件都是一个独立的小世界封装了自己的 HTML、CSS 和 JavaScriptApp.vue (根组件) ├── Header.vue │ ├── Logo.vue │ └── NavMenu.vue ├── Sidebar.vue │ └── UserProfile.vue └── Footer.vue1.2 为什么要使用组件优势说明复用性同一个组件可以在多个地方使用避免重复造轮子可维护性每个组件职责单一易于定位和修复问题可测试性独立组件可以单独进行单元测试协作开发不同开发者可以并行开发不同组件减少冲突关注点分离模板、逻辑和样式集中管理清晰明了1.3 组件的两种编写风格Vue 3 支持两种组件编写风格风格特点适用场景选项式 APIOptions API使用data、methods、computed等选项Vue 2 用户过渡或偏好传统风格组合式 APIComposition API使用setup和script setupVue 3 推荐逻辑复用更优雅本系列教程将默认使用组合式 API 的script setup语法这是 Vue 3 的官方推荐写法。二、单文件组件SFC的完整结构Vue 的单文件组件Single File Component简称 SFC使用.vue扩展名一个文件包含三个部分script setup langts // 1. 逻辑部分JavaScript/TypeScript // 响应式数据、方法、生命周期等 /script template !-- 2. 模板部分HTML -- !-- 描述组件的 UI 结构 -- /template style scoped /* 3. 样式部分CSS */ /* scoped 表示样式仅作用于当前组件 */ /style2.1script setup是 Vue 3.2 引入的编译时语法糖它极大地简化了组件的编写script setup langts // 导入的组件自动注册无需手动注册 import MyButton from ./MyButton.vue // 定义的变量和方法直接暴露给模板 const message Hello Vue! const count ref(0) function increment() { count.value } /script template div p{{ message }}/p MyButton clickincrement点击次数{{ count }}/MyButton /div /templatescript setup的核心特性顶层绑定自动暴露所有变量、函数、导入的组件都可以直接在模板中使用更简洁的代码不需要export default、setup()函数的返回值等更好的运行时性能编译时优化减少运行时代码更好的 TypeScript 支持类型推导更准确2.2 模板模板部分使用增强的 HTML 语法支持 Vue 的指令和插值template !-- 根元素可以是多个Vue 3 支持多根节点 -- h1{{ title }}/h1 p v-ifvisible可见内容/p button clickhandleClick点击/button slot/slot /templateVue 3 支持多根节点Fragment不再需要包裹一个额外的父元素。2.3scoped属性让样式仅在当前组件中生效style scoped /* 这个类名只在这个组件中生效 */ .title { color: blue; } /style原理Vue 会为组件中的每个元素添加一个唯一属性如data-v-7ba5bd90然后通过属性选择器限制样式范围。/* 编译后 */.title[data-v-7ba5bd90]{color:blue;}深度选择器:deep()如果需要穿透子组件样式style scoped /* 影响子组件中的 .child-class */ :deep(.child-class) { color: red; } /style三、组件的注册使用组件之前需要先注册。Vue 支持全局注册和局部注册两种方式。3.1 局部注册推荐在script setup中直接导入并使用组件自动注册!-- Parent.vue -- script setup // 导入组件自动注册 import ChildComponent from ./ChildComponent.vue import AnotherComponent from ./AnotherComponent.vue /script template div ChildComponent / AnotherComponent / /div /template非script setup方式较少使用script import ChildComponent from ./ChildComponent.vue export default { components: { ChildComponent // 显式注册 } } /script3.2 全局注册在应用入口main.ts中注册全局组件可以在任何子组件中使用无需再次导入// main.tsimport{createApp}fromvueimportAppfrom./App.vueimportGlobalButtonfrom./components/GlobalButton.vueconstappcreateApp(App)// 全局注册app.component(GlobalButton,GlobalButton)app.mount(#app)然后在任意组件中直接使用template GlobalButton / /template全局注册的优缺点优点缺点无需在组件中反复导入即使未使用也会被打包增加最终包体积适合基础组件如按钮、输入框依赖关系不明确不利于维护一次注册到处可用可能造成命名冲突最佳实践优先使用局部注册仅在确实需要全局可用如 UI 基础组件库时使用全局注册。3.3 组件命名规范场景推荐命名示例组件文件名PascalCase大驼峰MyComponent.vue模板中使用PascalCase 或 kebab-case短横线MyComponent或my-component全局注册名称PascalCase 或 kebab-caseapp.component(MyButton, ...)Vue 官方推荐在模板中使用PascalCase因为它更容易与原生 HTML 元素区分。四、Props父传子的数据传递Props属性是父组件向子组件传递数据的主要方式。Props 遵循单向数据流原则父级 props 的更新会流向子组件反之则不行。4.1 基本用法!-- 子组件Child.vue -- script setup langts // 定义 props使用 TypeScript 类型声明 defineProps{ title: string count: number isActive: boolean }() /script template div h2{{ title }}/h2 p数量{{ count }}/p p状态{{ isActive ? 激活 : 未激活 }}/p /div /template!-- 父组件Parent.vue -- script setup import Child from ./Child.vue import { ref } from vue const parentTitle ref(Hello Props) const parentCount ref(42) /script template Child :titleparentTitle :countparentCount :is-activetrue / /template关键规则使用v-bind:绑定动态值静态值直接写属性名字符串也可以不用:titleHello等价于:titleHello布尔属性不带值默认为trueis-active等价于:is-activetrue4.2 类型声明与默认值在script setup中使用类型声明定义 propsscript setup langts // 基本类型声明 defineProps{ name: string age: number isAdmin: boolean hobbies: string[] address?: { city: string; street: string } // 可选属性? }() /script如果需要默认值需要使用withDefaults编译宏script setup langts interface Props { title: string count?: number items?: string[] status?: pending | success | error } // 为可选属性设置默认值 const props withDefaults(definePropsProps(), { count: 0, items: () [默认项1, 默认项2], // 对象/数组需使用工厂函数 status: pending }) /script注意withDefaults是 Vue 的编译宏不需要导入直接在script setup中使用即可。4.3 非script export default { props: { title: { type: String, required: true }, count: { type: Number, default: 0 }, items: { type: Array, default: () [] }, status: { type: String, validator: (value) [pending, success, error].includes(value) } } } /script4.4 Props 的校验规则Vue 提供了丰富的 prop 校验能力script setup langts // 使用运行时声明而非仅类型声明支持校验 defineProps({ // 基础类型 propA: Number, // 多种类型 propB: [String, Number], // 必填 propC: { type: String, required: true }, // 默认值 propD: { type: Number, default: 100 }, // 对象默认值 propE: { type: Object, default: () ({ name: 默认 }) }, // 自定义校验 propF: { type: String, validator: (value: string) [small, medium, large].includes(value) } }) /scriptTypeScript 类型声明 vs 运行时声明方式优点缺点TS 类型声明类型安全、IDE 提示好无法在运行时校验和提供默认值运行时声明支持校验、默认值缺乏 TS 类型推断可以结合使用最佳实践同时使用两者发挥各自优势用 TS 类型提供静态类型用运行时声明提供校验和默认值。script setup langts interface Props { size?: small | medium | large count?: number } // 运行时声明 TS 类型 const props withDefaults(definePropsProps(), { size: medium, count: 0 }) /script4.5 单向数据流原则Props 是只读的子组件不能直接修改父组件传递的 propscript setup // ❌ 错误直接修改 prop const props defineProps{ count: number }() function increment() { props.count // ❌ 会触发警告 } /script修改 Props 的正确方式①子组件触发事件父组件修改推荐!-- 子组件 -- script setup const props defineProps{ count: number }() const emit defineEmits{ (e: update:count, value: number): void }() function increment() { emit(update:count, props.count 1) } /script②使用计算属性转换为本地数据script setup import { computed } from vue const props defineProps{ initialValue: number }() const localCount computed({ get: () props.initialValue, set: (val) emit(update:initialValue, val) }) /script4.6 Props 的命名约定在模板中使用camelCase驼峰或kebab-case短横线均可Vue 会自动转换!-- 父组件两种写法都支持 -- Child :userNamename :user-ageage /在 JavaScript 中始终使用camelCasedefineProps{userName:stringuserAge:number}()五、组件实例与 props 的关系5.1 每个组件拥有独立的状态当你多次使用同一个组件时每个实例都有独立的数据作用域!-- 父组件 -- template Counter / Counter / Counter / /template每个Counter实例都有自己独立的count状态互不影响。5.2 传递非字符串类型传递非字符串类型时必须使用v-bindtemplate !-- 字符串 -- Child name张三 / !-- 数字必须用 v-bind -- Child :age25 / !-- 布尔值 -- Child :is-activetrue / !-- 数组 -- Child :hobbies[阅读, 编程] / !-- 对象 -- Child :user{ name: 李四, age: 30 } / !-- 方法很少用 -- Child :callbackhandleCallback / /template5.3 动态传递多个 props对象展开如果有一个包含多个属性的对象可以使用v-bind的对象展开一次性传递script setup const user { name: 王五, age: 28, email: wangwuexample.com } /script template !-- 等价于传递 :nameuser.name :ageuser.age :emailuser.email -- UserCard v-binduser / /template六、实战构建一个卡片组件库我们通过一个完整的卡片组件示例综合运用本章所学知识!-- Card.vue - 通用卡片组件 -- script setup langts interface Props { title?: string subtitle?: string variant?: default | primary | success | warning | danger size?: small | medium | large bordered?: boolean hoverable?: boolean loading?: boolean } const props withDefaults(definePropsProps(), { title: , subtitle: , variant: default, size: medium, bordered: true, hoverable: false, loading: false }) // 计算样式类 const variantClass computed(() card-${props.variant}) const sizeClass computed(() card-${props.size}) /script template div classcard :class[ variantClass, sizeClass, { card-bordered: bordered }, { card-hoverable: hoverable }, { card-loading: loading } ] !-- 标题区域 -- div v-iftitle || subtitle classcard-header h3 v-iftitle classcard-title{{ title }}/h3 p v-ifsubtitle classcard-subtitle{{ subtitle }}/p /div !-- 内容区域插槽 -- div classcard-body slot nameheader/slot slot/slot slot namefooter/slot /div !-- 加载状态覆盖层 -- div v-ifloading classcard-overlay span classspinner/span /div /div /template style scoped .card { background: white; border-radius: 12px; padding: 20px; transition: all 0.3s ease; position: relative; overflow: hidden; } /* 尺寸变体 */ .card-small { padding: 12px 16px; } .card-medium { padding: 20px 24px; } .card-large { padding: 28px 32px; } /* 颜色变体 */ .card-default { background: white; } .card-primary { background: #ebf8ff; border-color: #4299e1; } .card-success { background: #f0fff4; border-color: #48bb78; } .card-warning { background: #fffff0; border-color: #ecc94b; } .card-danger { background: #fff5f5; border-color: #fc8181; } .card-bordered { border: 2px solid #e2e8f0; } .card-bordered.card-primary { border-color: #4299e1; } .card-bordered.card-success { border-color: #48bb78; } /* ... 其他变体 */ .card-hoverable:hover { transform: translateY(-4px); box-shadow: 0 10px 30px rgba(0,0,0,0.1); } .card-header { margin-bottom: 16px; padding-bottom: 12px; border-bottom: 1px solid #e2e8f0; } .card-title { font-size: 18px; font-weight: 600; color: #1a202c; margin: 0; } .card-subtitle { font-size: 14px; color: #718096; margin: 4px 0 0 0; } .card-body { color: #2d3748; } .card-loading { opacity: 0.6; pointer-events: none; } .card-overlay { position: absolute; inset: 0; background: rgba(255,255,255,0.7); display: flex; align-items: center; justify-content: center; } .spinner { width: 32px; height: 32px; border: 4px solid #e2e8f0; border-top-color: #4299e1; border-radius: 50%; animation: spin 0.8s linear infinite; } keyframes spin { to { transform: rotate(360deg); } } /style使用卡片组件template div classapp !-- 基础卡片 -- Card title欢迎使用 Vue 3 p这是一个基础的卡片组件展示内容区域。/p /Card !-- 带变体的卡片 -- Card title成功提示 subtitle操作已成功完成 variantsuccess :borderedtrue p您的数据已保存。/p template #footer button clickhandleAction确认/button /template /Card !-- 可悬浮的卡片 -- Card title产品卡片 variantprimary :hoverabletrue :loadingisLoading template #header div classproduct-image️/div /template div classproduct-info p商品名称Vue 3 完全指南/p p价格¥99.00/p /div template #footer button加入购物车/button /template /Card /div /template script setup import Card from ./components/Card.vue import { ref } from vue const isLoading ref(false) function handleAction() { alert(已确认) } /script七、本篇小结知识点核心内容关键要点组件本质可复用的 Vue 实例拥有独立模板、逻辑、样式组件树构成应用单文件组件.vue文件包含script setup、template、style scopedscript setup是 Vue 3 推荐写法局部注册在script setup中import后直接使用优先推荐按需加载全局注册app.component()全局注册适用于基础 UI 组件库Props 定义defineProps{ name: type }()使用 TS 类型声明Props 默认值withDefaults(definePropsProps(), { ... })对象/数组需用工厂函数单向数据流子组件不能直接修改 props通过事件通知父组件修改Props 校验运行时声明支持validator等可结合 TS 类型和运行时校验八、思考与练习基础练习创建一个Avatar组件接收src、sizesmall/medium/large、alt等 props并显示不同尺寸的头像。实践任务将上一节注册表单中的部分内容如输入框组、表单验证错误提示抽取为可复用的子组件使用 props 传递配置。陷阱排查以下代码存在什么问题!-- 子组件 -- script setup const props defineProps{ value: string }() function clear() { props.value } /script !-- 父组件 -- template Child :valueparentData / /template深度思考为什么 Vue 要强制单向数据流如果允许子组件直接修改父组件的 props会带来哪些问题下篇预告第 8 篇我们将全面深入地学习组件通信包括Props 和 Emit 的标准通信模式使用v-model在组件间双向绑定.sync修饰符的替代方案复杂场景下的组件通信策略准备好掌握组件间高效协作的能力

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询