Vue 2.0学习笔记:组件的使用

发布于 大漠

从这一节开始正式进入对Vue 2.0组件的系统学习。在Vue中,组件是最强大的功能之一。而且Vue组件涉及到的知识点也非常的多,比如组件的使用,prop、事件、slots以及动态动组等等。在一节的内容中无法全部涵盖这些知识点。所以将会分几节内容来整理Vue组件中的学习笔记。

什么是组件

什么是组件?围绕这个问题,我查阅了这方面的相关资料,特别是几位大神@hax@飞叔@云龙有关于组件相关的方面的阐述,让我受益非浅。建议大家多花点时间先阅读几位大神整理的组件相关的内容:

我是一位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 不能包含其它的交互元素(如按钮,链接)
  • ulol 只能直接包含 li
  • select 只能包含 optionoptgroup
  • table 只能直接包含 thead, tbody, tfoot, tr, caption, col, colgroup
  • tr 只能直接包含 thtd

在自定义组件中使用这些受限制的元素时会导致一些问题,例如:

<table>
    <my-row>...</my-row>
</table>

自定义组件<my-row>会被当作无效的内容,因此会导致错误的渲染结果。变通的方案是使用特殊的is特性:

<table>
    <tr is="my-row"></tr>
</table>

应当注意,如果使用来自以下来源之一的字符串模板,则没有这些限制:

  • <script type="text/x-template">
  • JavaScript 内联模板字符串
  • .vue 组件

因此,请尽可能使用字符串模板。

组件的eldata选项

传入Vue构造器的多数选项也可以用在Vue.extend()Vue.component()中,不过有两个特列:datael。Vue规定:

在定义组件的选项时,datael选项必须使用函数。

实际上,如果你这么做:

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的组件有很深入的了解以及丰富的经验,欢迎在下面的评论中与我们一起分享。最后希望这篇文章对初学者有所帮助。

大漠

常用昵称“大漠”,W3CPlus创始人,目前就职于手淘。对HTML5、CSS3和Sass等前端脚本语言有非常深入的认识和丰富的实践经验,尤其专注对CSS3的研究,是国内最早研究和使用CSS3技术的一批人。CSS3、Sass和Drupal中国布道者。2014年出版《图解CSS3:核心技术与案例实战》。

如需转载,烦请注明出处:https://www.fedev.cn/vue/component-registered.htmlKyrie 1 Dark Grey Silver Grey Dark Reflect Black Green Glow 705277-001