本章概要
- 全局注册与本地注册
- 使用prop向子组件传递数据
组件时 Vue.js 最核心的功能,在前端应用程序中可以采用模块化的开发,实现可重用,可扩展。
组件时带有名字的可复用的实例,因此在在根组件实例中的各个选项,在组件中也一样可以使用。
通过组件系统,可以使用独立可复用的小组件构建大型应用,几乎任意类型的应用的界面都可以抽象为一个组件树。
10.1 全局注册与本地注册
与自定义指令类似,组件也有两种注册方式:全局注册与本地注册。全局注册组件使用应用程序实例的 component() 方法注册,该方法接受两个参数,第一个参数是组件的名字,第二个参数是一个函数对象,或者是一个选项对象。
语法形式如下:
app.component({string} name,{Function | Object} definition (optional))
本地注册是在组件实例的选项对象中使用 components 选项注册。
下面代码为一个全局注册组件的例子:
const app = Vue.createApp({});
app.component('ButtonCounter', {
data() {
return {
count: 0
}
},
template: `
<button v-on:click="count++">
You clicked me {{ count }} times.
</button>`
});
app.mount('#app');
组件的内容通过 template 选项定义,当使用组件时,组件所在的位置将被 template 选项的内容所替换。
把组件当成自定义元素,按照元素的方式使用,元素的名称就是注册时指定的组件的名称。如下:
<div id="app">
<ButtonCounter></ButtonCounter>
</div>
不幸的是,上述代码并不能正常工作。
这是因为 HTML 并不区分元素和属性的大小写,所以浏览器会把所有大写字符解释为小写字符,即 buttonCounter ,而注册时使用的名称是 ButtonCounter,这就导致找不到组件而出现错误。
解决办法是在 DOM 模板中采用 kebab-case 命名引用组件。如下:
<div id="app">
<button-counter></button-counter>
</div>
只要组件注册时采用的是 PascalCase (首字母大写)命名,就可以采用 kebab-case ,命名来引用。不过,在非 DOM 模板中(如字符串模板或单文件组件内),是可以使用组件的原始名称的,即 ButtonCounter 和 button-counter 都是可以的。当然,如果想要保持名字的统一性,也可以在注册组件时,直接使用 kebab-case 命名来为组件命名。例如:
app.component('button-counter',...)
此外,由于 HTML 并不支持自闭合的自定义元素,所以在 DOM 模板中不要把 ButtonCounter 组件当做自闭合元素来使用。
当然,在非DOM 模板中没有这个限制,相反,还鼓励将没有内容的组件作为自闭合元素来使用,这可以明确该组件没有内容,由于省略了结束标签,代码也更简洁。
本例的渲染结果如下:
本地注册组件的代码如下:
const MyComponent = {
data() {
return {
count: 0
}
},
template:
`<button v-on:click="count++">
You clicked me {{ count }} times.
</button>`
}
const app = Vue.createApp({
components: {
ButtonCounter: MyComponent
}
}).mount('#app');
对于 components 选项对象,它的每个属性的名称就是自定义元素的名称,其属性值就是这个组件的选项对象。
全局注册组件可以在该应用程序中的任何组件实例的模板中使用,而局部注册组件只能在父组件的模板中使用。
10.2 使用prop向子组件传递数据
组件是当作自定义元素来使用的,元素一般是有属性的,同样,组件也可以有属性。
在使用组件时,为组件元素设置属性,但组件内部如何接收呢?
首先需要在组件内部注册一些自定义的属性,称为 prop ,这些prop 是在组件的 props 选项中定义的;之后,在使用组件时,就可以将这些 prop 的名称作为元素的属性名来使用,通过属性向组件传递数据,这些数据将作为组件实例的属性被使用。
<div id="app">
<post-item post-title="duang duang"></post-item>
</div>
<script>
const app = Vue.createApp({});
app.component('PostItem',{
// 声明props
props: ['postTitle'],
// postTitle就像data中定义的数据属性一样,
// 在该组件中可以像 "this.postTitle" 这样使用
template: '<h3>{{ postTitle }}</h3>'
})
app.mount('#app');
</script>
渲染结果为
<h3>duang duang</h3>
说明:
(1)在 props 选项中定义的 prop ,可以作为该组件实例的数据属性使用。
(2)props 选项中声明的每一个 prop ,在使用组件时,作为元素的自定义属性使用,属性值会被传递给组件内部的 prop 。
(3)HTML 中的属性名是不区分大小写的,浏览器在加载 HTML 页面时,会统一转换为小写字符,采用驼峰命名的 prop 名要使用其等价的 kebab-case(短横线分割命名)名称。如果在字符串模板或单文件组件内使用,则没有这个限制。
上述说明的第(3)点,通过下面的父子组件的例子,可以对字符串模板有更好的认识。
<div id="app">
<post-list></post-list>
</div>
<script src="https://unpkg.com/vue@next"></script>
<script>
const app = Vue.createApp({});
const PostItem = {
// 声明props
props: ['postTitle'],
// postTitle就像data中定义的数据属性一样,
// 在该组件中可以像 "this.postTitle" 这样使用
template: '<h3>{{ postTitle }}</h3>'
};
app.component('PostList', {
components: {
PostItem
},
// 在字符串模板中可以直接使用PascalCase命名的组件名
// 和camelCase命名的prop名
template: '<div><PostItem postTitle="duang duang"></PostItem></div>'
});
app.mount('#app');
</script>
在本地注册组件时,使用了 ES6 的属性初始值的简写语法。
在字符串模板中,除了各种命名可以直接使用外,组件还可以作为自闭和元素使用。
下面稍微修改一下 PostList 组件,给它定义一个数据属性 title,然后用title 的值给子组件 PostItem 的 postTitle 属性赋值。如下:
<script>
app.component('PostList', {
data() {
return {
title: 'duang duang'
}
},
components: {
PostItem
},
// 在字符串模板中可以直接使用PascalCase命名的组件名
// 和camelCase命名的prop名
template: '<div><PostItem postTitle="title"></PostItem></div>'
});
app.mount('#app');
</script>
渲染结果为:
<h3>title</h3>
因为在解析的时候,title并没有作为表达式来解析,而仅仅是作为一个静态的字符串值传递给 postTitle 属性。
与普通的 HTML 元素的属性传值一样,要想接收动态值,需要使用 v-bind 指令,否则,接收的值都是静态的字符串值。
修改 PostList 组件的模板字符串,如下:
template: '<div><PostItem :postTitle="title"></PostItem></div>'
渲染结果如下:
<h3>duang duang</h3>
如果组件需要接收多个传值,那么可以定义多个 prop 。如下:
<div id="app">
<post-list></post-list>
</div>
<script src="https://unpkg.com/vue@next"></script>
<script>
const app = Vue.createApp({});
const PostItem = {
props: ['author','title','content'],
template: `
<div>
<h3>{{ title }}</h3>
<p> 作者:{{ author }}</p>
<p>{{ content }}</p>
</div>`
};
app.component('PostList', {
data() {
return {
author: '余华',
title: '活着',
content: '管斤'
}
},
components: {
PostItem
},
// template: '<div><PostItem :post="post"/></div>'
template:`<div> <PostItem :author="author" :title="title" :content="content"> </PostItem> </div>`
});
app.mount('#app');
</script>
从例子中可以看到,如果子组件定义的 prop 较多,调用时就需要写较多的属性,然后一一赋值,这很麻烦。
为此,可以使用不带参数的 v-bind 命令传入一个对象,只需要将该对象的属性和组件的 prop 一一对应即可。如下:
<div id="app">
<post-list></post-list>
</div>
<script src="https://unpkg.com/vue@next"></script>
<script>
const app = Vue.createApp({});
const PostItem = {
props: ['post'],
template: `
<div>
<h3>{{ post.title }}</h3>
<p> 作者:{{ post.author }}</p>
<p>{{ post.content }}</p>
</div>`
};
app.component('PostList', {
data() {
return {
post:{
author: '余华',
title: '活着',
content: '管斤'
}
}
},
components: {
PostItem
},
template: '<div><PostItem :post="post"/></div>'
});
app.mount('#app');
</script>