组件是Vue应用的基本构建块。通过组件,我们可以将UI拆分成独立的、可复用的部分。每个组件都有自己的状态和逻辑,可以独立开发和测试。组件化开发让代码更加模块化、可维护,也更容易复用。
在这一节,我们将学习如何创建和使用组件,了解组件的注册方式,以及如何使用Props向组件传递数据。这些知识是构建复杂Vue应用的基础。

组件可以理解为自定义的HTML元素。与HTML中内置的<div>、<button>这样的标签类似,Vue允许我们创建属于自己的元素,比如<user-card>(用户信息卡片)、<todo-item>(待办事项条目)等。这些自定义元素其实就是组件。每个组件封装了特定的结构、样式和行为,在不同的地方都可以复用。
组件不仅能作为页面的基本构建块,还能拥有自己的独立数据和逻辑,实现高度内聚、低耦合。通过组件化,我们可以把一个复杂的界面按照功能拆分成很多小模块,每个模块各自负责自己的部分,开发、调试、维护都变得更加简单。
下面,通过一个简单的例子来展示什么是组件,以及它们如何让开发变得更加灵活和强大:
|<div id="app"> <button-counter></button-counter> <button-counter></button-counter> <button-counter></button-counter> </div> <script> const { createApp } = Vue; // 定义一个组件 const ButtonCounter = { data() { return { count: 0 }; }, template: ` <button @click="count++"> 点击了 {{ count }} 次 </button> ` }; const app = createApp({}); // 注册组件 app.component('button-counter', ButtonCounter); app.mount('#app'); </script>
在这个例子中,我们定义了一个ButtonCounter组件,它有一个按钮和一个计数器。然后我们在应用中注册了这个组件,就可以在模板中使用<button-counter>标签了。
每个<button-counter>实例都有自己独立的count状态,互不影响。这就是组件的优势:我们可以创建可复用的UI组件,每个实例都有自己独立的状态。
在Vue 3中,组件的定义主要有两种方式:选项式API(Options API)和组合式API(Composition API)。
选项式API是一种通过对象配置来声明组件各部分的方式。我们通常在组件对象里定义如 data、methods、computed、props 等选项,每个选项负责处理组件的某一方面。
组合式API则是Vue 3新增的一种定义组件逻辑的方法。通过在 setup() 函数中组合响应式数据和函数,可以更加灵活地组织和复用逻辑,尤其适合逻辑复杂的场景。
下面我们先详细看看如何使用选项式API来定义一个组件:
|<script> const ButtonCounter = { data() { return { count: 0 }; }, methods: { increment() { this.count++; } }, template: ` <div> <button @click="increment">点击了 {{ count }} 次</button> </div> ` }; </script>
这是一个使用选项式API定义的组件。它有一个data函数返回组件的状态,有一个methods对象定义方法,有一个template字符串定义模板。
我们也可以使用组合式API来定义组件:
|<script> const ButtonCounter = { setup() { const { ref } = Vue; const count = ref(0); const increment = () => { count.value++; }; return { count, increment
这是使用组合式API定义的同一个组件。它使用setup函数来设置组件的状态和逻辑,然后返回需要在模板中使用的数据和方法。
在定义好组件之后,只有将其注册后才能在模板中作为自定义元素使用。在Vue中,组件注册有两种方式:全局注册和局部注册。
全局注册的组件是指一旦注册后,可以在整个 Vue 应用中的任意模板里被直接引用,而无需在每个父组件中单独声明。也就是说,不论该组件是在根组件,还是在任意子组件的模板内,只要全局注册过,都可以像使用原生标签一样直接使用这个自定义组件。
|<div id="app"> <button-counter></button-counter> </div> <script> const { createApp } = Vue; const ButtonCounter = { data() { return { count: 0 }; }, template: `<button @click="count++">点击了 {{ count }} 次</button>`
在这个例子中,我们使用app.component()全局注册了ButtonCounter组件。注册后,我们可以在应用的任何地方使用<button-counter>标签。
全局注册的优点是使用方便,缺点是即使不使用也会被打包,增加应用体积。
局部注册是指组件只在被显式注册的父组件内部可用,无法在其他组件或全局范围内直接使用。具体来说,只有在某个父组件的 components 选项中注册的子组件,才能在该父组件的模板里以自定义标签的形式引用。
这样做的好处是有效控制组件的作用域,避免全局污染,同时能实现更高的灵活性和按需加载。例如:
|<div id="app"> <parent-component></parent-component> </div> <script> const { createApp } = Vue; const ChildComponent = { template: `<p>这是子组件</p>` }; const ParentComponent = { components: { 'child-component'
在这个例子中,我们在ParentComponent的components选项中注册了ChildComponent。这样,ChildComponent只能在ParentComponent中使用,不能在其他地方使用。
组件通常是可复用的,但每个组件实例可能需要接收不同的数据以完成不同的任务。为了让父组件能够向子组件传递数据,Vue 提供了 props(属性)机制。通过在子组件中声明 props,父组件可以在使用自定义标签时以属性的形式向子组件传递参数。 这样,子组件就能根据接收到的数据渲染不同的内容,提高了组件的重用性和灵活性。props 不但可以传递普通的字符串、数字,也能传递布尔值、对象、数组等不同类型的数据,从而让组件满足各种需求。 例如,一个用户信息卡片组件可以通过 props 显示不同用户的姓名和年龄,这样就能在页面上复用多次而展示不同的数据内容。
让我们看一个例子:
|<div id="app"> <user-card name="张三" age="25"></user-card> <user-card name="李四" age="30"></user-card> </div> <script> const { createApp } = Vue;
在这个例子中,我们定义了一个UserCard组件,它接收name和age两个props。在模板中,我们可以像使用普通数据一样使用props。
注意,在模板中使用props时,我们使用{{ name }}而不是{{ props.name }}。Vue会自动将props暴露到组件实例上。
除了可以在模板中直接以静态属性的方式为子组件传递props之外,我们还可以利用v-bind指令来实现动态传递props。v-bind允许我们将父组件中的变量或表达式的值绑定到子组件的props上,这样当父组件中的数据发生变化时,props也会自动更新传递到子组件,实现数据的双向联动。
例如,如果父组件有多个用户信息对象,我们可以通过v-bind动态传递不同用户的数据到子组件,从而复用同一个组件展示不同的内容。
|<div id="app"> <user-card :name="user1.name" :age="user1.age"></user-card> <user-card :name="user2.name" :age="user2.age"></user-card> </div> <script> const { createApp, ref } = Vue;
在这个例子中,我们使用:name和:age来动态绑定props。当user1或user2的值改变时,组件会自动更新。
我们也可以直接传递整个对象:
|<div id="app"> <user-card :user="user1"></user-card> <user-card :user="user2"></user-card> </div> <script> const { createApp, ref } = Vue; const UserCard = {
在这个例子中,我们传递整个user对象作为prop。在组件中,我们可以通过user.name和user.age来访问数据。
在定义props时,我们可以为每个prop指定其数据类型。这不仅可以让代码更易于维护,也有助于在开发时发现类型不匹配的问题。Vue支持多种常见类型,包括:
String:字符串类型。例如 用户名、标题等文本数据。Number:数字类型,如价格、数量、年龄等。Boolean:布尔类型,常用于开关、状态(如 true/false)。Array:数组类型,可以传递一个列表,如标签列表等。Object:对象类型,可传递结构化数据,如一个用户对象、配置对象等。Date:日期类型,用于时间相关的数据。Function:函数类型,可以作为回调传递给子组件。通过在props定义中指定类型,Vue会在检测到类型不匹配时在控制台发出警告。这能帮助我们提早发现潜在的问题,提升组件的稳定性。
|<div id="app"> <product-card name="商品1" :price="100" :in-stock="true" :tags="['热销', '推荐']" ></product-card> </div> <script> const { createApp } = Vue;
在这个例子中,我们为每个prop指定了类型。如果传递的类型不匹配,Vue会在控制台发出警告。
在 Vue 中,除了为 props 指定类型外,还可以通过在 props 配置对象中添加 default 属性来为某个 prop 设置默认值。当父组件没有传递对应的 prop 时,组件就会自动使用你定义的默认值。
这样可以保证组件在缺失某些数据时依然能够正常工作,不会因为缺少 prop 而导致报错或出现异常情况。默认值可以是一个普通的值(如数字、字符串、布尔值),也可以是一个返回默认值的工厂函数(适用于对象或数组类型),以避免多组件实例间的数据互相影响。
|<div id="app"> <user-card name="张三"></user-card> <user-card name="李四" age="30"></user-card> </div> <script> const { createApp } = Vue; const UserCard =
在这个例子中,name是必需的(required: true),age有默认值18。如果父组件没有传递age,它会使用默认值。
在 Vue 中,我们可以通过在 props 的配置对象中添加 validator 函数,为每个 prop 制定自定义的验证规则。这样当父组件传递的 prop 值不符合我们设定的要求时,Vue 会在浏览器的控制台中输出警告信息,而不会阻止组件的正常渲染。
每个 validator 函数接收当前 prop 的值作为参数,需要返回一个布尔值(true 表示验证通过,false 表示未通过)。这种方式非常适合对 props 进行更复杂和灵活的校验,比如限定字符串长度、范围、格式等,从而提升组件的可用性和健壮性。
|<div id="app"> <user-card name="张三" :age="25" email="zhangsan@example.com"></user-card> </div> <script> const { createApp } = Vue; const UserCard = { props: { name: { type: String,
在这个例子中,我们为每个prop添加了验证器。name必须至少2个字符,age必须在0到150之间,email必须是有效的邮箱格式。如果验证失败,Vue会在控制台发出警告。
在Vue中,props实现了单向数据流,即数据总是从父组件流向子组件。父组件通过props向子组件传递数据,子组件只能读取和使用这些数据,而不能直接修改传入的props。如果子组件需要修改这些数据,应该通过触发事件(如$emit)通知父组件,由父组件更新后再通过props传递新的值。 这样可以保证数据流动的可控性和应用状态的一致性。
|<div id="app"> <p>父组件的count:{{ count }}</p> <child-component :count="count"></child-component> </div> <script> const { createApp, ref } = Vue; const ChildComponent = { props: ['count'
在这个例子中,子组件接收count作为prop,但不能直接修改它。如果子组件需要修改数据,应该通过事件通知父组件,让父组件来修改。
Props是只读的,子组件不应该直接修改props。如果需要修改,应该通过事件通知父组件,或者使用计算属性来创建基于prop的派生值。
让我们通过一个更详细的综合示例来巩固所学的知识。我们将重新实现一个“待办事项”列表应用,代码会拆分为多个组件:用于输入新事项的表单组件(TodoForm)、用于展示列表的组件(TodoList)、以及用于显示每一项的单个事项组件(TodoItem)。 父组件负责管理待办事项的数据和逻辑,子组件通过props传递数据,通过事件将用户行为通知给父组件,实现数据的单向流动和组件的协作。
|<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>组件基础示例</title> <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
在这个例子中,我们创建了三个组件:TodoForm用于添加待办事项,TodoList用于显示待办列表,TodoItem用于显示单个待办项。每个组件都有自己的职责,通过props和事件进行通信。
在这一个部分,我们学习了组件的基础知识。我们了解了如何定义和注册组件,如何使用Props向组件传递数据,以及Props的类型、默认值和验证。 组件是Vue应用的基本构建块。通过组件化开发,我们可以将复杂的应用拆分成小的、可复用的部分,让代码更加模块化和可维护。
然而,这些还远远不够,在下一节,我们将学习组件通信,了解如何使用事件、provide/inject等方式在组件之间传递数据。