定义
组件 (Component) 是 Vue.js 最强大的功能之一。组件可以扩展 HTML 元素,封装可重用的代码。在较高层面上,组件是自定义元素,Vue.js 的编译器为它添加特殊功能。在有些情况下,组件也可以表现为用 is
特性进行了扩展的原生 HTML 元素。所有的 Vue 组件同时也都是 Vue 的实例,所以可接受相同的选项对象 例如 data
、computed
、watch
、methods
以及生命周期钩子等。仅有的例外是像 el
这样根实例特有的选项。
全局组件
我们知道,可以通过以下方式创建一个Vue实例:
new Vue({
el:"#app",
//选项
})
要注册一个全局组件,可以使用 Vue.component(tagName, options)
。例:
Vue.component('my-component',{
//选项
})
组件在注册之后,便可以作为自定义元素 <my-component></my-component>
在一个实例的模板中使用。注意确保在初始化根实例之前注册组件:
<div id="app">
<!-----引用组件----->
<my-component></my-component>
</div>
<script>
// 注册
Vue.component('my-component', {
template: '<h3>我是全局组件!</h3>'
})
// 创建根实例
new Vue({
el: '#app'
})
</script>
全局注册的组件可以用在其被注册之后的任何 (通过 new Vue
) 新创建的 Vue 根实例,也包括其组件树中的所有子组件的模板中。
局部组件
你不必把每个组件都注册到全局。你可以通过某个 Vue 实例/组件的实例选项 components注册仅在其作用域中可用的组件:
<div id="app">
<!-----引用组件----->
<my-component></my-component>
</div>
<script>
var Child = {
template: '<h3>我是局部组件!</h3>'
}
new Vue({
el:"#app",
components: {
// <my-component> 将只在父组件模板中可用
'my-component': Child
}
})
</script>
你也可以通过一个普通的 JavaScript 对象来定义组件:
var ComponentA = { /* ... */ } var ComponentB = { /* ... */ } var ComponentC = { /* ... */ } |
然后在 components
选项中定义你想要使用的组件:
new Vue({ el: '#app', components: { 'component-a': ComponentA, 'component-b': ComponentB } }) |
对于 components
对象中的每个属性来说,其属性名就是自定义元素的名字,其属性值就是这个组件的选项对象。
注意局部注册的组件在其子组件中不可用。例如,如果你希望 ComponentA
在 ComponentB
中可用,则你需要这样写:
var ComponentA = { /* ... */ } var ComponentB = { components: { 'component-a': ComponentA }, // ... } |
或者如果你通过 Babel 和 webpack 使用 ES2015 模块,那么代码看起来更像:
import ComponentA from './ComponentA.vue' export default { components: { ComponentA }, // ... } |
注意在 ES2015+ 中,在对象中放一个类似 ComponentA
的变量名其实是 ComponentA: ComponentA
的缩写,即这个变量名同时是:
- 用在模板中的自定义元素的名称
- 包含了这个组件选项的变量名
解析DOM模板时的注意事项
有些 HTML 元素,诸如 <ul>
、<ol>
、<table>
和 <select>
,对于哪些元素可以出现在其内部是有严格限制的。而有些元素,诸如 <li>
、<tr>
和 <option>
,只能出现在其它某些特定的元素内部。
这会导致我们使用这些有约束条件的元素时遇到一些问题。例如:
|
这个自定义组件 <blog-post-row>
会被作为无效的内容提升到外部,并导致最终渲染结果出错。幸好这个特殊的 is
特性给了我们一个变通的办法:
|
需要注意的是如果我们从以下来源使用模板的话,这条限制是不存在的:
- 字符串 (例如:
template: '...'
) - 单文件组件 (
.vue
) <script type="text/x-template">
因此,请尽可能使用字符串模板。
上面的意思是下面的例子是会不行的:
<div id="app">
<select>
<optioncomp></optioncomp>
</select>
</div>
<script>
new Vue({
el: '#app',
components:{
'optioncomp':{
template: '<option >a</option>'
}
}
})
</script>
但是用is特殊属性可以:
<div id="app">
<select>
<option is="optioncomp"></option>
</select>
</div>
<script>
new Vue({
el: '#app',
components:{
'optioncomp':{
template: '<option >a</option>'
}
}
})
</script>
或者temp模板标签也可以:
<div id="app">
<select>
<option is="optioncomp"></option>
</select>
<!--模板内容存放区域-->
<script type="x-template" id="optioncompTemp">
<option>a</option>
</script>
</div>
<script>
new Vue({
el: '#app',
components:{
'optioncomp':{ template: '#optioncompTemp' }
}
})
</script>
或者内联模板字符串也行
<div id="app">
<selectcomp></selectcomp>
</div>
<script>
Vue.component('optioncomp',
{ template: '<option >a</option>' });
new Vue({
el: '#app',
components:{
'selectcomp':{
template: ' <select><optioncomp></optioncomp></select>'
}
}
})
</script>
data必须是函数
构造 Vue 实例时传入的各种选项大多数都可以在组件里使用。只有一个例外:data
必须是函数。因此每个实例可以维护一份被返回对象的独立的拷贝:
data: function () {
return {
count: 0
}
}
如果 Vue 没有这条规则,点击一个按钮就可能会像如下代码一样影响到其它所有实例:
<div id="components-demo">
<button-counter></button-counter>
<button-counter></button-counter>
<button-counter></button-counter>
</div>
<script>
var data={
count:0
}
// 定义一个名为 button-counter 的新组件
Vue.component('button-counter', {
data: function () {
return data
},
template: '<button v-on:click="count++">You clicked me {{ count }} times.</button>'
})
new Vue({ el: '#components-demo' })
</script>
由于这三个组件实例共享了同一个 data
对象,因此递增一个 counter 会影响所有组件!我们可以通过为每个组件返回全新的数据对象来修复这个问题:
data: function () {
return {
count: 0
}
}
注意当点击按钮时,每个组件都会各自独立维护它的 count
。因为你每用一次组件,就会有一个它的新实例被创建。