从这一节开始正式进入对Vue 2.0组件的系统学习。在Vue中,组件是最强大的功能之一。而且Vue组件涉及到的知识点也非常的多,比如组件的使用,prop
、事件、slots
以及动态动组等等。在一节的内容中无法全部涵盖这些知识点。所以将会分几节内容来整理Vue组件中的学习笔记。
什么是组件
什么是组件?围绕这个问题,我查阅了这方面的相关资料,特别是几位大神@hax、@飞叔和@云龙有关于组件相关的方面的阐述,让我受益非浅。建议大家多花点时间先阅读几位大神整理的组件相关的内容:
- 《关于前端开发中“模块”和“组件”概念的思考》by @hax
- 《2015前端组件化框架之路》by @飞叔
- 《Web应用的组件化:基本思路》by @飞叔
- 《Web应用的组件化:管控平台》by @飞叔
- 《前端工程与模块化框架 》by @云龙
我是一位CSSer,在很多时候也会聊模块化和组件相关的概念,接受这方面最早的概念来自于Bootstrap这个CSS Framework。后来我更喜欢Brad Frost提出的原子设计(Atomic Design):
如果从这个角度出发,Web中的任何一个元素(对应原子设计中的Atoms原子),都可以把其当作一个组件,比如最常见的按钮。另外也可以把由多个原子构成的构建(Molecules分子),也可以当作是一个组件,比如一个搜索表单。
在CSSer的世界中,经常会把Web中可复用的部分划分为组件。组件即是使用一个到多个元素(Atoms原子)组成的任何界面部分。比如下面的三个卡片,虽然在外观上长得不全一致,但他用到的元素近乎是一样的。
但需要注意的是,组件并不一定需要模块化。
组件和模块化两者有什么区别,强烈建议阅读贺老(@hax)的《关于前端开发中“模块”和“组件”概念的思考》一文。
或许这样理解组件有点粗陋,那么我们来看看Vue官网对组件是怎么定义的:
组件(Component)是Vue最强大的功能之一。组件可以扩展HTML元素,封装可重用的代码。在较高层面上,组件是自定义元素,Vue的编译器为它添加特殊功能。在有些情况下,组件也可以表现为用
is
特性进行了扩展的原生HTML元素。所有的Vue组件同时也都是Vue的实例,所以可以接受相同的选项对象(除了一些根级特有的选项)并提供相同的生命周期钩子。
Vue提供一个组件系统,提供了一种抽象,让我们可以使用独立可复用的小组件来构建大型应用,任意类型的应用界面都可以抽象为一个组件树:
越来越感觉组件类似原子设计中的,原子、分子和组织。而整个应用界面类似于原子设计中的模板和页面。
组件的创建
既然Vue的组件是一个非常强大的特性,那么我们首要要了解的是在Vue中怎么创建组件。@ANTHONYGORE在他的一篇博文中介绍了七种创建Vue组件的方式。不管使用哪种方式,创建Vue的组件都有三个基本步骤:创建组件构造器、注册组件和使用组件。
比如,我们创建一个Button
组件:
// 1. 创建一个组件构造器
let myButton = Vue.extend({
template: `<button>点击我</button>`
})
// 2. 注册组件,并指定组件的标签,组件的HTML标签为<my-button>
Vue.component('my-button', myButton)
// 创建Vue实例
let app = nue Vue({
el: '#app'
})
<!-- 3. #app是Vue实例挂载的元素 -->
<div id="app">
<my-button />
</div>
添加点CSS样式,看到的效果如下:
通过浏览器开发者工具查看,使用Vue的组件<my-button />
和使用HTML的元素<button>
最终得到的结果没啥区别。
创建这样一个简单的组件并不是很困难的事情,对于初学者而言,是要理解怎么创建组件。咱们一起来看看组件的创建和注册:
Vue.extend()
是Vue构造器的扩展,调用Vue.extend()
创建的是一个组件构造器Vue.extend()
构造器有一个选项对象,选项对象的template
属性用于定义组件要渲染的HTML,简单的理解这个属性用来定义组件的模板(也就是组件的HTML结构)- 使用
Vue.component()
注册组件,在注册组件时需要提供两个参数,第一个参数是组件的标签,比如上例中的my-button
,第二个参数是组件构造器,比如上例中的myButton
- 组件应该挂载到某个Vue实例下,否则它不会生效。这一点需要特别的注意。另外同一个组件可以同时挂载到多个Vue实例下
全局注册
我们已经知道,可以通过以下方式创建一个Vue实例:
let app = new Vue({
el: '#app'
})
并且使用Vue.component(tagName, options)可以注册一个组件,而且使用这种方式注册的组件是一个全局的,这意味着该组件可以在任意Vue实例下使用。比如:
Vue.component('my-button', myButton)
其中myButton
是通过Vue.extend()
方法构建的,除此之外,咱们还可以这样写:
Vue.component('my-button', {
template: `<button>点击我</button>`
})
请注意,对于自定义标签的命名,Vue不强制遵循W3C规则(小写,并且包含一个短杠),尽管这被认为是最佳实践。
组件注册之后,便可以作为自定义元素<my-button />
在一个实例的模板中使用。注意确保在初始化根实例之前注册组件:
<div id="app">
<my-button />
</div>
局部注册
在Vue中,不必把每个组件都注册到全局。你也可以通过某个Vue实例/组件的实例选项components
注册,使用该选项注册的组件被称为局部注册,言外之意,该组件只能在对应的Vue实例中使用,如果别的Vue实例调用该组件,将会报一个提示错误。比如,把上面的全局注册的组件,换成局局部注册:
let myButton = Vue.extend({
template: `<button>点击我</button>`
})
let app = new Vue({
el: 'app',
components: {
'my-button': myButton
}
})
<div id="app">
<my-button />
</div>
得到的效果和注册全局组件是一样的。不同的是,如果你在另一个Vue实例中调用注册的局部组件,改组件不会生效。比如在app2
这个实例中调用app
中注册的组件my-button
,就不会生效。
<div id="app2">
<my-button />
</div>
通过这个示例说明了注册全局组件和注册局部组件的不同方法,以用其运用范围:
- 通过
Vue.component(tagName, options)
注册全局组件,可以在任何Vue实例范围中使用 - 通过Vue实例的
components
属性注册局部组件,只能在该实例范围中使用
组件注册语法糖
以上组件注册的方式有些繁锁,Vue为了简化组件注册的过程,提供了注册语法糖。如果你仔细的话,前面也简单的提到过一下。那么这里特意提出来,组件注册语法糖。先来看使用Vue.component()
直接创建和注册组件:
// 注册全局组件 my-button
Vue.component('my-button', {
template: `<button>点击我</button>`
})
let app = new Vue({
el: '#app'
})
Vue.component()
的第一个参数是组件标签名称,第二个参数是一个选项对象,使用选对象的template
属性定义组件模板。使用这种方式,Vue在背后会自动调用Vue.extend()
来创建组件构造器。
接下来看在选项对象components
属性中注册局部组件的语法糖:
let app = new Vue({
el: '#app',
components: {
'my-button': {
template: `<button>点击我</button>`
}
}
})
尽管注册组件的语法糖简化了组件注册,但在template
选项中拼接HTML元素还是相当的麻烦,尽管ES6的语法让事情变得简单了不少,但也将导致HTML和JavaScript的高耦合性。
庆幸的是,Vue除了上面这些语法糖之外,还提供了其他的方式。比如x-template
:
Vue.component('my-button', {
template: '#my-button'
})
<script type="text/x-template" id="my-button">
<button>点击我</button>
</script>
内联模板inline-template
方式:
Vue.component('my-button',{
// ...
})
<my-button inline-template>
<button>点击我</button>
</my-button>
除了上述方式,还有<template>
、render()
函数、JSX以及单文件组件等方式。有关于这方面的详细介绍,可以点击这里。
不过使用DOM模板解析时(例如,使用el
选项来把Vue实例挂载到一个已有内容的元素上),你会受到HTML本身的一些限制,因为Vue只有在浏览器解析、规范化模反之后才能获取内容。尤其要注意,像<ul>
、<ol>
、<table>
、<select>
这样的元素里允许包含的元素有限制,而另一些像<option>
这样的元素只能出现在某些特定元素的内部。
a
不能包含其它的交互元素(如按钮,链接)ul
和ol
只能直接包含li
select
只能包含option
和optgroup
table
只能直接包含thead
,tbody
,tfoot
,tr
,caption
,col
,colgroup
tr
只能直接包含th
和td
在自定义组件中使用这些受限制的元素时会导致一些问题,例如:
<table>
<my-row>...</my-row>
</table>
自定义组件<my-row>
会被当作无效的内容,因此会导致错误的渲染结果。变通的方案是使用特殊的is
特性:
<table>
<tr is="my-row"></tr>
</table>
应当注意,如果使用来自以下来源之一的字符串模板,则没有这些限制:
<script type="text/x-template">
- JavaScript 内联模板字符串
.vue
组件
因此,请尽可能使用字符串模板。
组件的el
和data
选项
传入Vue构造器的多数选项也可以用在Vue.extend()
或Vue.component()
中,不过有两个特列:data
和el
。Vue规定:
在定义组件的选项时,
data
和el
选项必须使用函数。
实际上,如果你这么做:
Vue.component('my-component', {
template: '<span>{{ message }}</span>',
data: {
message: 'hello'
}
})
那么 Vue 会停止运行,并在控制台发出警告,告诉你在组件实例中 data
必须是一个函数。但理解这种规则为何存在也是很有益处的,所以让我们先作个弊:
<div id="example-2">
<simple-counter></simple-counter>
<simple-counter></simple-counter>
<simple-counter></simple-counter>
</div>
var data = {
counter: 0
}
Vue.component('simple-counter', {
template: '<button v-on:click="counter += 1">{{ counter }}</button>',
// 技术上 data 的确是一个函数了,因此 Vue 不会警告,
// 但是我们却给每个组件实例返回了同一个对象的引用
data: function () {
return data
}
})
new Vue({
el: '#example-2'
})
由于这三个组件实例共享了同一个 data
对象,因此递增一个 counter
会影响所有组件!这就错了。我们可以通过为每个组件返回全新的数据对象来修复这个问题:
data: function () {
return {
counter: 0
}
}
现在每个 counter
都有它自己内部的状态了。
组件组合
组件设计初衷就是要配合使用的,最常见的就是形成父子组件的关系:组件A
在它的模板中使用了组件B
。它们之间必然需要相互通信:父组件可能要给子组件下发数据,子组件则可能要将它内部发生的事情告诉父组件。然而,通过一个良好定义的接口来尽可能将父子组件解耦也是很重要的。这保证了每个组件的代码可以在相对隔离的环境中书写和理解,从而提高了其可维护性和复用性。
在Vue中,父子组件的关系总结为prop
向下传递,事件向上传递。父组件通过prop
给子组件下发数据,子组件通过事件给父组件发送消息。看看它们是怎么工作的。
咱们先不深入了解父子组件关系,以及他们的通讯方式。我们先来看看父子组件的创建和使用姿势:
// 构造子组件
let myButton = Vue.extend({
template: `<button>Search</button>`
})
// 构造父组件
let mySearch = Vue.extend({
// 在父组件mySearch中使用<my-button>标签
template: `
<!-- 注意,有多个标签时,需要用一个容器将他们包裹起来 -->
<div>
<label for="search">Search the site</label>
<input type="search" name="search" id="search" placeholder="Enter keyword" />
<my-button />
</div>
`,
components: {
// 局部注册myButton组件,该组件只能在mySearch组件内使用
'my-button': myButton
}
})
// 注册全局组件mySearch
Vue.component('my-search', mySearch)
let app = new Vue({
el: '#app'
})
添加一点样式。我们看到的效果如下:
分几个步骤来理解这段代码:
- 先使用
Vue.extend()
定义了一个myButton
组件构造器和mySearch
组件构造器 components: {'my-button', myButton}
将myButton
组件注册到mySearch
组件,并将myButton
组件的标签设置为my-button
- 在
mySearch
组件内通过template
标签,定义了搜索表单mySearch
组件所需要的模板,并且引用了myButton
组件 Vue.component('my-search', mySearch)
全局注册mySearch
组件- 在页面中使用
my-search
标签,挂截到app
实例中,渲染整个mySearch
组件,其子组件myButton
也将被渲染出来
总结
在这一节中,我们学习了Vue中组件的一些简单概念,并且掌握怎么通过Vue.extend()
来构造组件,Vue.component()
来注册全局组件以及components
属性来注册局部组件。并且简单的学习了一些注册组件语法糖和创建组件的一些方式。文章中的示你的组件都是极其简单的,比如我们的按钮组件,事实上和HTML中写按钮一样的效果,那是我们对Vue的组件了解的还不够透彻,我相信随着后面的学习,我们可以把这个组件做得更完美。比如给他赋予事件,修改按钮内容,根据按钮状态修改颜色等等。
由于本人是Vue的初学者,如果文章中有不对之处,还请各种大婶拍正。如果你对Vue的组件有很深入的了解以及丰富的经验,欢迎在下面的评论中与我们一起分享。最后希望这篇文章对初学者有所帮助。
如需转载,烦请注明出处:https://www.w3cplus.com/vue/component-registered.html