0、入门
在正式开始讲解组件之前,我们先来看一个简单的例子:
<!DOCTYPE html> <html> <head> <title>Demo</title> <script src="https://cdn.jsdelivr.net/npm/vue"></script> </head> <body> <div id="app"> <button-counter></button-counter> </div> <script> Vue.component('button-counter', { data: function () { return { count: 0 } }, template: '<button v-on:click="count++">{{ count }} times</button>' }) new Vue({ el: '#app' }) </script> </body> </html>
下面我们详细解读一下上面这份代码:
Vue.component('button-counter', { data: function () { return { count: 0 } }, template: '<button v-on:click="count++">{{ count }} times</button>' })
我们首先通过全局方法 Vue.component()
创建了一个名为 button-counter 的全局组件
该方法的第一个参数是组件名,第二个参数是选项对象,对象中包含两个属性,data
和 template
属性 data
是一个 返回对象的函数,用于储存动态变化的数据
之所以定义为函数,是因为组件可能被用来创建多个实例,若定义为对象,则所有实例将会共享同一份数据对象
属性 template
是一个 模板字符串,用于定义组件的 HTML 代码
需要注意的是,组件必须是单根元素,也就是说模板的内容必须包裹在一个父元素内
<div id="app"> <button-counter></button-counter> </div>
然后,我们就可以在一个通过 new Vue()
创建的根实例中,把这个组件当作自定义元素使用
好,在对组件有了一个初步的理解之后,下面我们再来进行详细的学习
1、组件注册
组件是可复用的 Vue实例,在使用组件前,我们首先要对组件进行注册,以便于 Vue 能够识别出来
(1)组件注册的参数有两个,分别是 组件名 和 选项对象
- 组件名
定义组件名的方式有两种,分别是 kebab-case(短横线分隔命名)和 PascalCase(首字母大写命名)
kebab-case
:在引用时,需要使用 kebab-case
PascalCase
:在模板中使用时,两种命名法都可用;在 DOM 中使用时,只有 kebab-case 是有效的
- 选项对象
该对象接收的选项与 new Vue()
接收的选项类似,仅有的例外是像 el
这样的根实例特有的选项
(2)组件注册的方式有两种,分别是 全局注册 和 局部注册
- 全局注册
我们可以使用全局方法 Vue.component()
进行全局注册,其第一个参数是组件名,第二个参数是选项对象
全局注册的组件可以在任何新创建的根实例中使用
Vue.component('component-a', { /* ... */ }) Vue.component('component-b', { /* ... */ }) new Vue({ el: '#app' })
- 局部注册
我们可以在创建根实例时用选项 components
进行局部注册,它是一个对象,键是组件名,值是选项对象
局部注册的组件不可以在其子组件中使用,也就是说,在下例中的两个组件不可以在各自内部相互调用
var ComponentA = { /* ... */ } var ComponentB = { /* ... */ } new Vue({ el: '#app', components: { 'component-a': ComponentA, 'component-b': ComponentB } })
如果希望 ComponentA
在 ComponentB
中可用,我们需要换一种写法:
var ComponentA = { /* ... */ } var ComponentB = { components: { 'component-a': ComponentA }, // ... }
2、向子组件传递数据 —— prop
prop 是在组件注册的一些自定义特性,当一个值传递给一个 prop 特性时,它就变成该组件实例的一个属性
(1)传递静态 prop
在下例中,我们给 prop 传递了一个静态的值,Title Here
<!DOCTYPE html> <html> <head> <title>Demo</title> <script src="https://cdn.jsdelivr.net/npm/vue"></script> </head> <body> <div id="app"> <title-item title="Title Here"></title-item> </div> <script> Vue.component('title-item', { props: ['title'], template: '<h3>{{ title }}</h3>' }) new Vue({ el: '#app' }) </script> </body> </html>
(2)传递动态 prop
在下例中,我们通过 v-bind
给 prop 绑定了一个动态的对象,content
<!DOCTYPE html> <html> <head> <title>Demo</title> <script src="https://cdn.jsdelivr.net/npm/vue"></script> </head> <body> <div id="app"> <title-item v-bind:title="content.title"></title-item> </div> <script> Vue.component('title-item', { props: ['title'], template: '<h3>{{ title }}</h3>' }) new Vue({ el: '#app', data: { content: { 'title': 'Title Here' } } }) </script> </body> </html>
(3)prop 类型与 prop 验证
在上面的两个例子中,props 都是一个字符串数组,其中的每一个 prop 都是一个字符串
但事实上,prop 还可以是其它类型
这时我们可以用对象列出 prop,其中对象的键是 prop 的名称,对象的值是 prop 的类型
Vue.component('my-component', { props: { propA: String, propB: Number, propC: Boolean, propD: Array, propE: Object, propF: Function // ... }, // ... })
既然 prop 有了类型,就要判断 prop 是否符合类型,我们可以定制 prop 的验证方式(以下是官方文档中的一个例子)
Vue.component('my-component', { props: { // 基础的类型检查 (`null` 和 `undefined` 会通过任何类型验证) propA: Number, // 多个可能的类型 propB: [String, Number], // 必填的字符串 propC: { type: String, required: true }, // 带有默认值的数字 propD: { type: Number, default: 100 }, // 带有默认值的对象 propE: { type: Object, // 对象或数组默认值必须从一个工厂函数获取 default: function () { return { message: 'hello' } } }, // 自定义验证函数 propF: { validator: function (value) { // 这个值必须匹配下列字符串中的一个 return ['success', 'warning', 'danger'].indexOf(value) !== -1 } } } })
当 prop 验证失败时,(开发环境构建版本的) Vue 将会产生一个控制台的警告
3、向父组件传递数据 —— 自定义事件
prop 是一个单向下行绑定,即父级 prop 的更新会向下流动到子组件中,但反过来不行
如果子组件要把数据传递给父组件,则需要使用自定义事件
父组件可以通过 v-on
监听子组件实例的任意事件,而子组件可以通过 $emit()
触发事件
(1)监听子组件事件
<!DOCTYPE html> <html> <head> <title>Demo</title> <script src="https://cdn.jsdelivr.net/npm/vue"></script> </head> <body> <div id="app"> <p>{{ total }}</p> <button-counter v-on:increment="incrementTotal"></button-counter> </div> <script> Vue.component('button-counter', { template: '<button v-on:click="incrementHandler">{{ counter }}</button>', data: function () { return { counter: 0 } }, methods: { incrementHandler: function () { this.counter += 1 this.$emit('increment') } }, }) new Vue({ el: '#app', data: { total: 0 }, methods: { incrementTotal: function () { this.total += 1 } } }) </script> </body> </html>
下面让我们来详细解读一下上面这段代码:
Vue.component('button-counter', { template: '<button v-on:click="incrementHandler">{{ counter }}</button>', data: function () { return { counter: 0 } }, methods: { incrementHandler: function () { this.counter += 1 this.$emit('increment') } }, })
首先,我们定义了一个名为 button-counter 的组件
子组件 button-counter 使用 v-on
监听原生事件 click,该事件的处理函数是 incrementHandler()
在 incrementHandler()
中,首先将 counter(子组件中的数据)的值加 1,然后触发自定义事件 increment
<div id="app"> <p>{{ total }}</p> <button-counter v-on:increment="incrementTotal"></button-counter> </div>
new Vue({ el: '#app', data: { total: 0 }, methods: { incrementTotal: function () { this.total += 1 } } })
根组件同样是通过 v-on
监听自定义事件 increment,该事件的处理函数是 incrementTotal()
在 incrementTotal()
中,将 total(根组件中的数据)的值加 1
(2)通过事件抛出一个值
我们可以在 $emit()
函数的第二个参数中抛出一个值
Vue.component('button-counter', { template: '<button v-on:click="incrementHandler">{{ counter }}</button>', data: function () { return { counter: 0 } }, methods: { incrementHandler: function () { this.counter += 1 this.$emit('increment', 2) } }, })
并在事件处理函数的第一个参数中接收该值
new Vue({ el: '#app', data: { total: 0 }, methods: { incrementTotal: function (value) { this.total += value } } })
文章知识点与官方知识档案匹配,可进一步学习相关知识