Vue 组件详解笔记:从概念到实战(初学者必学)

        各位小伙伴,组件是 Vue 的核心灵魂!学会组件,我们才能真正用 Vue 做组件化开发 —— 把页面拆成一个个可复用的 “积木块”,比如导航栏、卡片、按钮,既方便维护又能提高效率。这篇笔记会带我们从 “什么是组件” 开始,一步步学会创建、导入、使用组件,还有最关键的 “父组件给子组件传值”,全程带代码示例和易错点提醒,跟着学完就能上手写自己的组件啦!

一、先搞懂:什么是 Vue 组件?

        ·我们先从最基础的概念入手,搞清楚 “组件到底是什么”“为什么要用组件”。

1.1 组件的本质:可复用的 Vue 实例

        Vue 组件就是一个可复用的、封装了模板(HTML)、逻辑(JS)和样式(CSS)的 Vue 实例,每个以.vue为后缀的文件,都是一个独立的组件。

        比如我们之前见过的HelloWorld.vueHomeView.vue,本质上都是组件 —— 它们可以被多次引用(比如在不同页面用同一个按钮组件),也可以嵌套(比如首页组件里包含导航栏组件)。

1.2 组件的 3 大组成部分(.vue 文件结构)

        每个.vue组件都由 3 个顶层代码块构成,各司其职,缺一不可的只有<template>

代码块 作用说明 必选 / 可选 关键注意点
<template> 定义组件的 HTML 结构,负责 “显示什么” 必选 Vue 2 中必须有一个根元素(比如最外层包一个<div>);Vue 3 支持多根元素,但推荐保留单根,避免混乱
<script> 写组件的 JS 逻辑,负责 “做什么”(定义数据、方法、接收父组件传值等) 可选 export default导出组件,让其他组件能导入使用
<style> 定义组件的 CSS 样式,负责 “长什么样” 可选 scoped属性后,样式只作用于当前组件(不会污染其他组件,强烈推荐加)
基础组件示例(PersonDetail.vue):
<template>
  <!-- 模板:单根元素包裹,显示内容 -->
  <div class="person-detail">
    <h3>我是个人信息组件</h3>
  </div>
</template>

<script>
// 逻辑:导出组件,定义组件名
import { defineComponent } from 'vue'
export default defineComponent({
  name: 'PersonDetail' // 组件名(调试时用,推荐和文件名一致)
})
</script>

<style scoped>
// 样式:scoped确保只影响当前组件
.person-detail {
  color: #333;
  margin: 20px 0;
}
</style>

1.3 用 “搭积木” 理解组件化开发

        我们可以把整个 Vue 应用比作 “一栋房子”,组件就是 “积木块”:

  • 房子(整个应用)= 多个大积木(页面组件,如 HomeView、AboutView)+ 多个小积木(通用组件,如导航栏、按钮);
  • 每个积木(组件)都有自己的形状(模板)、功能(逻辑)和颜色(样式)
  • 我们可以重复用同一个积木(比如多个页面用同一个按钮组件),也能把小积木拼到大积木里(比如首页组件里放导航栏组件)。

1.4 组件化开发的 4 大优势

        为什么一定要用组件?因为它能解决 “代码混乱、难维护” 的问题

  1. 可复用性:写一次组件,在多个页面引用(比如 “添加按钮” 组件,在用户页、商品页都能用),不用重复写代码;
  2. 可维护性:组件独立封装,出问题只改这一个文件(比如导航栏样式要改,只改导航栏组件),不影响其他部分;
  3. 协作高效:团队分工时,甲写导航栏组件,乙写列表组件,不用互相等,开发速度快;
  4. 测试友好可以单独测试一个组件(比如测试按钮点击功能),不用测整个页面。

二、组件的核心流程:创建→导入→使用

        我们从实战出发,一步步实现 “创建子组件→在父组件中导入→使用子组件” 的完整流程,以 “创建 PersonDetail 子组件,在 Components 父组件中使用” 为例。

2.1 步骤 1:创建单文件组件(子组件)

        首先,在src/components文件夹下新建PersonDetail.vue(子组件,负责显示个人信息),代码如下:

<template>
  <div>
    <h3>我是一个子组件....</h3>
  </div>
</template>

<script>
// 引入defineComponent:主要作用是提供TypeScript类型推断(即使不用TS,加它也更规范)
import { defineComponent } from 'vue'

// 导出组件:让其他组件能导入
export default defineComponent({
  name: 'PersonDetail' // 组件名,推荐和文件名一致(PersonDetail)
})
</script>
关键知识点:
  • defineComponent的作用:不用 TypeScript 也可以加,它能帮 Vue 更好地识别组件选项(比如nameprops),避免语法错误;
  • 组件文件放哪?通用组件(按钮、卡片)放src/components页面级组件(首页、关于页)放src/views这样结构更清晰。

2.2 步骤 2:在父组件中导入子组件

        接下来,我们在src/components下新建Components.vue(父组件),并导入PersonDetail子组件。导入需要做 2 件事:

  1. import语句引入子组件;
  2. components选项中声明子组件(不声明用不了,易错点!)。

父组件代码(Components.vue):

<template>
  <!-- 后续在这里使用子组件 -->
  <div class="parent-components"></div>
</template>

<script>
// 1. 导入子组件:@代表src目录,路径要写对(相对路径../也可以)
import PersonDetail from '@/components/PersonDetail.vue'
// 或相对路径:import PersonDetail from './PersonDetail.vue'(因为在同一文件夹)

import { defineComponent } from 'vue'
export default defineComponent({
  name: 'Components', // 父组件名
  // 2. 声明子组件:必须在这里写,才能在template中使用
  components: {
    PersonDetail // 键值对简写,等价于 PersonDetail: PersonDetail
  }
})
</script>
易错点提醒:
  1. 路径写错:比如把@/components/PersonDetail.vue写成@/component/PersonDetail.vue(少个 s),会报 “找不到模块”;
  2. 没在 components 声明:导入后不写components: { PersonDetail },直接在 template 用<PersonDetail/>,会报 “Unknown custom element”(未知自定义元素);
  3. 命名不一致:导入时写import Pd from './PersonDetail.vue',则components里要写Pd,template 里用<Pd/>(推荐导入名和组件名一致,避免混乱)。

2.3 步骤 3:在父组件中使用子组件

        导入并声明后,就能在父组件的<template>中像用 HTML 标签一样使用子组件了,标签名就是components中声明的名称(这里是PersonDetail)。

        修改父组件(Components.vue)的模板部分:

<template>
  <div class="parent-components">
    <h2>我是父组件(Components.vue)</h2>
    <!-- 使用子组件:标签名和components中声明的一致 -->
    <PersonDetail/>
    <!-- 还能重复使用:多次引用同一个子组件 -->
    <PersonDetail/>
  </div>
</template>

<script>
// 导入和声明代码不变(同上一步)
import PersonDetail from '@/components/PersonDetail.vue'
import { defineComponent } from 'vue'
export default defineComponent({
  name: 'Components',
  components: { PersonDetail }
})
</script>

<style scoped>
.parent-components {
  padding: 20px;
  border: 1px solid #eee;
}
</style>
效果:

        启动项目后,父组件会显示 “我是父组件”,下面跟着两个 “我是一个子组件....”—— 这说明我们成功在父组件中使用了子组件,还实现了组件复用!

三、核心通信:父组件给子组件传值(用 props)

        组件之间经常需要 “传数据”,比如父组件有 “刘备” 的名字和年龄,要传给子组件显示。Vue 中父给子传值的核心就是props,我们分步骤讲清楚。

3.1 什么是 props?

  props是子组件用来接收父组件传递数据的 “接口” —— 父组件把数据 “送” 到 props 里,子组件从 props 里 “拿” 数据来用。

特点:

  • 单向传递:只能父组件传给子组件,子组件不能直接改父组件传过来的 props(改了会报错,易错点!);
  • 命名一致:父组件传的 “参数名” 和子组件 props 接收的 “参数名” 必须完全一致(比如父传names,子不能写name)。

3.2 父给子传值的 4 个步骤(实战)

        还是用 “Components 父组件” 给 “PersonDetail 子组件” 传值为例,传递 3 个数据:message(信息标题)、names(姓名)、age(年龄)。

步骤 1:父组件准备数据并传递

父组件(Components.vue)中,在使用子组件的标签上,通过 “属性” 传递数据。分两种情况:

  • 静态传值:传递固定值(比如字符串 “个人信息”),直接写属性名="值"
  • 动态传值:传递变量(比如父组件的userName变量),要加v-bind:或简写:(比如:names="userName")。

这里先演示静态传值,父组件代码:

<template>
  <div class="parent-components">
    <h2>我是父组件(Components.vue)</h2>
    <!-- 父组件给子组件传值:属性名=值 -->
    <PersonDetail 
      message="个人信息"  <!-- 静态传值:固定字符串 -->
      names="刘备"        <!-- 静态传值:姓名 -->
      age="50"           <!-- 静态传值:年龄(注意:这里是字符串"50") -->
    />
  </div>
</template>

<script>
// 导入和声明子组件(不变)
import PersonDetail from '@/components/PersonDetail.vue'
import { defineComponent } from 'vue'
export default defineComponent({
  name: 'Components',
  components: { PersonDetail }
})
</script>
步骤 2:子组件用 props 接收数据

        子组件(PersonDetail.vue)中,在export default里添加props选项,声明要接收的参数(数组形式或对象形式,数组形式简单,对象形式更规范)。

        数组形式(简单,适合初学者):

<template>
  <div>
    <h3>我是一个子组件....</h3>
    <!-- 步骤3:子组件使用接收的props数据(直接用参数名) -->
    <p>信息标题:{{ message }}</p>
    <p>姓名:{{ names }}</p>
    <p>年龄:{{ age }}</p>
  </div>
</template>

<script>
import { defineComponent } from 'vue'
export default defineComponent({
  name: 'PersonDetail',
  // 步骤2:用props数组接收父组件传的参数(参数名必须和父组件一致)
  props: ['message', 'names', 'age'] 
})
</script>
步骤 3:子组件使用 props 数据

        子组件接收后,直接在<template>中用{{ 参数名 }}显示数据(不用像data里的变量那样return,直接用!),就是上面代码中<p>标签里的内容。

步骤 4:配置路由,测试效果

        我们需要配置路由,让父组件能通过 URL 访问,验证传值是否成功:

  1. 配置src/router/index.js(添加 Components 路由):
import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
// 导入Components父组件
import Components from '@/components/Components.vue'

const routes = [
  { path: '/', name: 'home', component: HomeView },
  { path: '/about', name: 'about', component: () => import('../views/AboutView.vue') },
  // 新增Components路由
  { path: '/components', name: 'components', component: Components }
]

const router = createRouter({
  history: createWebHistory(process.env.BASE_URL),
  routes
})

export default router
  1. App.vue中添加路由链接(让我们能点击访问):
<template>
  <div id="app">
    <nav>
      <router-link to="/">Home</router-link> |
      <router-link to="/about">About</router-link> |
      <router-link to="/components">Components(父组件)</router-link> <!-- 新增 -->
    </nav>
    <router-view/>
  </div>
</template>
  1. 测试:启动项目(npm run serve),点击 “Components(父组件)”,页面会显示:
我是父组件(Components.vue)
我是一个子组件....
信息标题:个人信息
姓名:刘备
年龄:50

        —— 这说明父组件的数值成功传给子组件了!

3.3 进阶:props 的对象形式(更规范)

        数组形式的 props 只能声明 “要接收哪些参数”,但不能限制参数类型(比如父传age="abc",子组件也能接收,导致显示错误)。推荐用对象形式的 props,指定参数类型、默认值、必填项,更严谨。

        修改子组件(PersonDetail.vue)的 props 为对象形式:

<script>
import { defineComponent } from 'vue'
export default defineComponent({
  name: 'PersonDetail',
  // props对象形式:指定类型、默认值、必填
  props: {
    // message:字符串类型,必填
    message: {
      type: String, // 类型:String(还支持Number、Boolean、Array等)
      required: true, // 必填:父组件必须传这个参数,不传会报警告
      default: '默认信息' // 默认值:如果父组件没传,就用这个(required为true时,default无效)
    },
    // names:字符串类型,必填
    names: {
      type: String,
      required: true
    },
    // age:数字类型,可选,默认值18
    age: {
      type: Number, // 类型:Number(父传的时候要传数字,不是字符串!)
      required: false,
      default: 18 // 默认值:父没传的话显示18
    }
  }
})
</script>
关键提醒:传值类型要匹配

如果子组件 props 指定ageNumber类型,父组件传值时要注意:

  • 错误:age="50"(传的是字符串 “50”,和 Number 类型不匹配,会报警告);
  • 正确::age="50"(加:动态传值,传的是数字 50,类型匹配)。

修改父组件的传值方式:

<PersonDetail 
  message="个人信息" 
  names="刘备" 
  :age="50"  <!-- 动态传值,确保是Number类型 -->
/>

3.4 props 的重要规则:不能直接修改

        子组件绝对不能直接修改 props 的值!比如子组件里写this.age = 51,会报错,因为 props 是父组件传的,要改只能让父组件改,子组件通过 “事件” 通知父组件(后面会讲子给父传值)。

错误示例(禁止!):

<script>
import { defineComponent } from 'vue'
export default defineComponent({
  name: 'PersonDetail',
  props: ['age'],
  methods: {
    changeAge() {
      this.age = 51 // 错误!不能直接改props
    }
  }
})
</script>

四、常见易错点总结(避坑指南)

        我们把前面提到的易错点汇总,帮大家避免踩坑:

  1. template 没有根元素(Vue 2)Vue 2 中<template>必须有一个根元素(比如外层包<div>),否则报错;Vue 3 支持多根,但推荐单根,保持兼容。

  2. 组件没在 components 选项中声明导入组件后,一定要在components: { 组件名 }中声明,否则用的时候会报 “Unknown custom element”。

  3. props 命名不一致父组件传的参数名(如names)和子组件 props 接收的参数名(如name)必须完全一致,大小写敏感(namesname)。

  4. 动态传值漏加:传动态值(变量、数字、布尔值)时,必须加:v-bind:,否则传的是字符串,导致类型不匹配。

  5. 子组件直接修改 propsprops 是父组件传的,子组件只能用不能改,要改必须通过事件通知父组件修改(后续讲子给父传值)。

  6. 组件路径写错导入组件时,路径要写对(@代表 src 目录),比如@/components/PersonDetail.vue,少个s或错个字母都会找不到模块。

五、总结:组件是 Vue 的核心

学到这里,我们已经掌握了 Vue 组件的核心:

  • 组件是.vue文件,包含<template>(显示)、<script>(逻辑)、<style scoped>(样式);
  • 组件使用流程:创建子组件→父组件导入→父组件声明→父组件使用;
  • 父给子传值用props,要注意类型匹配,不能直接修改 props。

组件是 Vue 组件化开发的基础,掌握这些,我们就能开始拆分页面(比如把首页拆成导航栏、轮播图、列表组件),后续还会学习 “子给父传值”“兄弟组件传值”“动态组件” 等进阶内容,一步步构建复杂应用!

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐