Vue组件基础

组件是Vue.js最推崇的,也是最强大的功能之一,核心目标是为了代码开发的重用性。我们可以把组件代码按照 template、style、script 的拆分方式,放置到对应的.vue文件中。本章节我们就来学习有关Vue组件的基础知识。

5.1 组件简介

组件的本质就是为了拆分Vue实例的代码量。我们也可以把所有的组件代码写到Vue实例中,那样的话,实例代码则显的臃肿不堪,冗长无比,这并不是我们想要的,所以,我们根据组件的功能性,以不同的的功能来划分不同的组件,将来我们需要什么样的功能,就可以取调用对应的组件即可,从而达到组件的可重用性和灵活性。

5.1.1 组件的定义

组件(Component)是Vue.js最强大的功能之一,组件可以扩展HTML元素,封装可重用代码,在较高层面上,组件是自定义元素,Vue.js的编译器为他添加特殊功能。有些情况下,组件也可以表现用 is 特性进行了扩展的原生的HTML元素,所有的Vue组件同时也都是Vue实例,所以可以接受相同的选项对象(除了一些根级特有的选项),并提供相同的生命周期钩子。

vue组件的功能:

5.1.2 组件的类型

什么情况下需要将组件注册为全局组件?一般是一些基础组件,频繁(3 次以上)需要用到的,需要全局注册。例如常用的 dialog(对话框)组件,search(搜索框) 组件,toast(弹出框) 组件,message(消息框)组件等。

一般情况下的组件应该是局部组件,这样会极大的减少构建应用后的代码体积,但是对于频繁使用的组件就显得麻烦了,所以建议,组件使用频率低,组件比较大的时候注册为局部组件。比如 table 组件,chart 组件等。

什么情况下需要将组件写为函数式组件?一般是无状态 (没有响应式数据)的组件可以注册成函数式组件,好像不用函数式组件也可以呀,为啥要注册成函数式组件?当一个组件是一个函数式组件的时候,它没有管理任何状态,也没有监听任何传递给它的状态,也没有生命周期方法。实际上,它只是一个接受一些 prop 的函数,所以渲染开销也低很多。

什么情况下需要将组件写为动态组件?一般是组件之间需要切换的情况下。但是不用动态组件也可以,那为啥要动态组件?当你导入 Dynamic Component1(动态组件) 从 1 写到 10 的时候,然后 template 再写 DynamicComponent 10 次的时候,它的好处就出来了。

在大型应用中,我们可能需要将应用分割成小一些的代码块,并且只在需要的时候才从服务器加载一个模块。为了简化,Vue 允许你以一个工厂函数的方式定义你的组件,这个工厂函数会异步解析你的组件定义。Vue 只有在这个组件需要被渲染的时候才会触发该工厂函数,且会把结果缓存起来供未来重渲染。

什么情况下需要将组件写为异步组件?一般是需要从服务器加载数据的组件,且需要多个地方使用的,因为它会把结果缓存起来供未来重渲染。还有就是大家使用最多的是在 Vue Router 里使用,异步组件结合 Webpack 的代码分割功能,可以轻松实现路由组件的懒加载。

组件是可以在它们自己的模板中调用自身的。不过它们只能通过 name 选项来做这件事。一般是组件需要调用自身的时候,比如树组件,侧边栏路由组件等,才会使用递归组件。

表5.1 组件的分类

分类标准

分类1

分类2

注册方式

全局组件

局部组件

有无自己的状态

无状态组件(函数式组件)

有状态组件(普通组件)

是否动态

动态组件

非动态组件

是否异步

异步组件

非异步组件

是否循环使用

递归组件

普通组件

以上是我们组件的分类,大家现在只需要了解组件的分类,后面我们会一一详解。

5.1.3 组件的构成要素

Vue.js的组件可以理解为预先定义好行为的ViewModel类。一个组件就像一个Vue实例一样,也可以预定义很多选项,但最核心的是以下几个:

在组件的构成要素中,我们发现data,methods,以及生命周期函数这些都是跟我们的Vue实例相似的,其实在功能上的初衷也是一样的,但是在使用上还是有差别的,接下来我们就来学习组件的封装和使用。

5.2组件封装和使用

5.2.1 封装组件

封装一个组件有以下三种方式:

表5.2封装组件的方法

方法

方法描述

Vue.extend()

使用extend()方法封装一个组件

Template

使用template标签来封装一个组件

Script

使用script标签来封装一个组件

封装好组件就可以直接使用组件了吗,我们仍然需要一系列的步骤,来看Vue组件的使用步骤:

  1. 使用任意的封装方法进行封装组件的内容;
  2. 使用Vue.component()方法进行组件的注册;
  3. 根据业务需要进行数据的通信;

了解完步骤后,我们知道组件根据注册方式的不同,分为全局和局部,接下来我们使用全局注册来演示Vue组件的封装的三种方式,现在先来看第一种方式。

例5-01 Demo0501.html

  1. Vue组件的第一种封装方式
  2. 这是根组件

程序的运行结果如下:

这就是我们自己封装的组件

图 5- 01 使用Vue.extend()方法封装的组件模板

根据例题Demo0501,我们来总结以下注意事项:

  1. 使用Vue.extend()方法来封装组件模板,模板内容必须使用{}括起来。根据编程常识,我们知道在{}中可以有多个选项,不止template一个选项。在5.1.3中我们学习了模板的构成要素,那么这些构成要素就可以放在{}中;
  2. 使用Vue.component("组件的名字",模板) 注册成为组件,在给组件起名字的时候是有注意事项的。在注册时,注册的名字是驼峰命名法,则在使用的时候,必须把大驼峰变为小写,中间加-,例如注册的名字为myCom,则在根容器中引入该名字标签的时候要使用。如果注册时没有使用驼峰命名,则不需要进行此转化,例如注册时hellocom,则在根标签使用的时候就是

接下来我们来看第一种封装方式的简化版本:

例5-02 Demo0502.html

  1. Vue组件的第一种封装方式
  2. 这是根组件

程序的运行结果如下:

这是自己的封装的组件,该组件中有两个html元素,所以必须有p根容器

图 5- 02 这是使用extend()封装的简化版本

通过例5-02,进行如下总结:

我们发现组件封装的模板是有html和css组成的;同时,也发现,在双引号中写html元素是一件很不方便的事情,为了解决这个问题,接下来我们使用template标签来实现第二种方式的封装;

例5-03 Demo0503.html,使用template标签进行封装;

  1. Vue组件的第二种封装方式
  2. 这是根组件

程序的运行结果如下:

这是使用template标签封装的组件

图 5- 03 template标签封装模板

根据例5-03我们总结以下:

template标签必须写到body中,给template标签一个id属性,方便注册的时候通过id引入;

无论是使用哪种方式封装模板,如果模板中有多个html元素,都必须使用根标签包裹;

接下来我们来看第三种方式封装组件:

例5-04 Demo0504.html,使用script标签进行封装;

  1. Vue组件的第三种封装方式
  2. 这是根组件

程序的运行结果如下:

这是使用scirpt标签封装的组件

图 5-04 这是使用script标签封装的组件

通过demo0504,总结如下:

使用script标签封装组件模板,最好在body标签外部定义,这样能很好的识别;必须指定type='text/x-template',声明id方便注册的时候通过id引入。

以上就是我们封装组件的三种使用方式,同时我们也学习了组件的使用步骤,第一,要封装内容,第二,注册组件,目前我们使用的是全局注册,第三,根据业务进行逻辑处理,这个后续讲解。

5.2.2 注册组件

在上一节中,我们使用三种封装方式以及全局注册对组件的使用有了清晰的了解,接下来,详细讲解组件的注册。

组件的注册有两种,全局注册和局部注册。

全局注册使用Vue.component(组件的注册名字,组件),使用该方法注册的组件可以在多个Vue实例中共享,我们来看实例:

例5-05 Demo0505.html,全局注册组件。

  1. 全局注册组件
  2. 实例1

  3. 实例1的根组件


  4. 实例2

  5. 实例2的根组件

程序的运行结果如下:

这是全局注册测组件

这是全局注册测组件

图 5-05 这是全局注册组件

通过例5-05,总结如下:

使用Vue.component()实现全局注册,全局注册的组件可以共享;一个页面可以写多个Vue实例,不同的实例控制不同的DOM元素。接下来,我们来看看局部组件:

例5-06 Demo0506.html,局部注册组件。

  1. 全局注册组件
  2. 实例1

  3. 实例1的根组件


  4. 实例2

  5. 实例2的根组件

程序的运行结果如下:

图 5-06 局部注册组件

通过demo0506结果我们发现,在两个实例中都使用了新注册的组件,但是只在实例1中显示,这说明局部注册的组件不具备共享性。同时发现,局部组件的注册的时候,需要在某一个Vue实例中使用components属性,那么在哪个Vue实例中注册就只能在哪个Vue实例中共享。

想一想:全局注册的组件换个页面引入还可以使用吗?

全局注册的组件只在当前页面的多个Vue实例中共享,并不能跨页面。

5.2.3 开发组件

通过对组建的初步了解,上述案例都是在html页面中定义组件,其实,真正在开发的时候,我们是把根据功能划分不同的组件,每个组件都是以.vue结尾的文件,每个文件都是有三部分组成,分别是template,script,style部分,如下所示:

以上的写法,我们在学习了vue与webpack结合后,就会大量使用,现在大家先做了解。

5.3组件数据通信

在实际开发过程中,Vue.js中的组件会与所在的环境进行通信,总结起来,组件通信有三种数据传递方式:

接下来我们分别对每一种数据通信进行详细的阐述。

5.3.1 props

"props”是组件数据的一个字段,期望从父组件传下来数据。因为组件实例的作用域是孤立的,这意味着不能并且不应该在子组件的模板内直接引用父组件的数据,所以子组件需要显式地用props选项来获取父组件的数据。props选项可以是字面量,也可以是表达式,还可以绑定修饰符。下面我们详细看一下它是如何使用的。

1.字面量语法

我们可以给子组件传一个常量值,也就是字面量,来看看子组件如何接受:

接下来演示普通常量值的传递,如例9-15所示。

例5-07 Demo0507.html

  1. props字面量的传值
  2. 这是根组件

程序的运行结果如下:

这是子组件,其中span标签内的内容是通过props接受过来的

图 5-07 传递字面常量

例5-07中,通过在子组件上自定义了一个属性“hello”,然后给这个属性一个字面常量,在子组件的内部通过props属性来接受。接受到后,可以正常使用插值表达式进行显示。

注意:props这个数组里写的是自定义属性的名字,所以是字符串。

自定义属性的名字,如果是kebab-case命名法,则在props接受得时候转为驼峰命名,例如

,则在子组件使用props接受得时候必须写成props:[‘myHello’]

2.动态语法

类似于用v-bind将HTML 特性绑定到一个表达式,我们也可以用v-bind将动态将父组件得数据绑定到自定义属性中。每当父组件的数据变化时,该变化也会传导给子组件,代码示例如下:

接下来演示动态绑定,如例5-08所示。

例5-08 Demo0508.html

  1. props字面量的传值
  2. 这是根组件

程序的运行结果如下:

图 5-08 动态传递父组件的数据

例5-08中,如果自定义属性要绑定父组件data中的数据,则需要使用v-bind,v-bind简写为冒号。

3.props类型

通常,我们只看到了以字符串数组形式列出的 props,例如,props: ['title', 'likes', 'isPublished', 'commentIds', 'author']。但是,如果你希望每个 prop 都有指定的值类型。这时,你可以以对象形式列出 prop,这些 property 的名称和值分别是 prop 各自的名称和类型:

  1. props: {
  2. title: String,
  3. likes: Number,
  4. isPublished: Boolean,
  5. commentIds: Array,
  6. author: Object,
  7. callback: Function,
  8. contactsPromise: Promise // or any other constructor
  9. }

type类型有如下:

4.props验证

我们可以为组件的 prop 指定验证要求,如果有一个需求没有被满足,则 Vue 会在浏览器控制台中警告你。为了定制 prop 的验证方式,你可以为 props 中的值提供一个带有验证需求的对象,而不是一个字符串数组。例如:

  1. let vm = new Vue({
  2. el: '#app',
  3. data: {
  4. msg: "我是父组件得数据"
  5. },
  6. components: {
  7. 'test': {
  8. props: {
  9. // 基础的类型检查 (`null` 和 `undefined` 会通过任何类型验证)
  10. propA: Number,
  11. // 多个可能的类型
  12. propB: [String, Number],
  13. // 必填的字符串
  14. propC: {
  15. type: String, //数据类型
  16. required: true //必填项
  17. },
  18. // 带有默认值的数字
  19. propD: {
  20. type: Number,
  21. default: 100 //默认值
  22. },
  23. // 带有默认值的对象
  24. propE: {
  25. type: Object,
  26. // 对象或数组默认值必须从一个工厂函数获取
  27. default: function () {
  28. return { message: 'hello' }
  29. }
  30. },
  31. // 自定义验证函数
  32. propF: {
  33. validator: function (value) {
  34. // 这个值必须匹配下列字符串中的一个
  35. return ['success', 'warning', 'danger'].indexOf(value) !== -1
  36. }
  37. }
  38. },
  39. template: '#myCom',
  40. data() {//这个是组件自己的数据
  41. return {
  42. hi: 'hello'
  43. }
  44. },
  45. }
  46. },
  47. });

注意:props的数据来自父级,data中数据是组件自己的数据。

子组件中的data和Vue实例中的data是不一样的,子组件的data是个函数,而且数据必须定义在return的对象中。

5.单项数据流

所有的 prop 都使得父子 prop 之间形成了一个单向向下的数据流:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外变更父级组件的状态,从而导致你的应用的数据流向难以理解。

父组件===>子组件:vue允许的,会主动触发的,也叫正向传递。

子组件===>父组件:vue允许的,不会主动触发,需要手动(被动)触发,叫做逆向传递。

另外,每次父级组件发生变更时,子组件中所有的 prop 都将会刷新为最新的值。这意味着你不应该在一个子组件内部改变 通过prop传递的值,如果你这样做了,Vue 会在浏览器的控制台中发出警告。

例5-09 Demo0509.html,通过

  1. props字面量的传值
  2. 这是根组件

程序的运行结果如下:

图 5-09 使用验证方式接受传值

如例5-09 显示,我们使用的是验证方式接受父组件传过来的值。接下来我们对父组件穿过的数据及性能修改,控制台警告如下:

一旦修改父组件传过来的值,浏览器的控制台发出警告,不允许修改父组件的数据

通过文本框修改父组件传过来的数据

图 5- 10 通过子组件修改父组件的数据

5.3.2 组件通信

上一节我们研究了父组件将值传递给子组件,叫做正向传值,子组件将值传递给父组件,叫做逆向传值;需要借助自定义事件。

vue.js 中允许正向传值,所以正向传值不需要条件触发,是主动的;逆向传值,也是允许的,但是需要主动(手动)触发,需要借助事件触发器:

使用步骤:

第一步:在父组件中引用的子组件的标签上,自定义事件;

这是根组件

显示子组件传过来的值:{{sonmsg}}


第二步:在子组件的模板中,定义事件函数;

第三步:在第二步骤的函数中,触发事件;

components: {

'test': {

template: '#myCom',

data() {//用来存放局部组件数据

return {

name: ""

}

},

methods: {//用来放局部组件的方法

sonfn() {

this.$emit("getmsg", this.name);

}

},

}

}

接下来演示子组件向父组件传值完整案例,如例5-10所示。

例5-10 Demo0510.html

  1. 子组件向父组件传值
  2. 这是根组件

  3. 显示子组件传过来的值:{{sonmsg}}

程序的运行结果如下:

图 5- 11 子组件向父组件传值

通过例5-10,总结如下:

模板对象的构成要素中,我们使用了template,data,methods这些选项;template指的是组建的模板内容,data里面放的是局部组件内部的数据,methods放的是组件内部的方法;data是个函数,存放的数据必须放在return的对象中;子组件传值给父组件,必须在子组件中通过点击事件对应的函数来触发父组件中自定义的事件。

5.3.3 slot使用

我们在构建页面过程中一般会把用的比较多的公共的部分抽取出来作为一个单独的组件,但是在实际使用这个组件的时候却又不能完全的满足需求,我们希望在这个组件中添加一点东西,这时候就需要用到插槽来分发内容。插槽(slot)是对组件的扩展,通过slot插槽向组件内部指定位置传递内容,通过slot可以父子传参。slot又可以分为三类,分别是匿名插槽,具名插槽以及作用域插槽。

1.匿名插槽

匿名插槽,顾名思义,就是没有名字的插槽。

插槽的具体使用步骤:

第一:在子组件中定义内容,但是对于不确定的内容使用slot占位置;

第二:注册子组件;

第三:在父组件中调用子组件,同时把不确定的内容补充上。

具体使用我们来看例5-11,如下:

例5-11 Demo0511.html

  1. 子组件向父组件传值
  2. 这是根组件


  3. 我是通过插槽添加到子组件的内容

程序的运行结果如下:

图 5- 12 匿名插槽

例5-11中,跟普通组件定义的区别,就是在定义子组件模板的时候多了个slot标签,而这个slot标签的作用就是为子组件中不确定的html内容占位置。在父组件中调用子组件的时候,在子组件内部写的span标签就会自动插入到slot标签所占的位置。

2.具名插槽

假设我们的电脑主板上的各种插槽,有插CPU的,有插显卡的,有插内存的,有插硬盘的,所以假设有个组件是computer,我们不可能把显卡插到内存的位置上,具名slot也就是每个slot都有名字,不能随意替换,要对应插入,接下来我们演示具名插槽的使用。

例5-12 Demo0512.html

  1. 子组件向父组件传值
  2. 这是根组件


程序的运行结果如下:

图 5- 13 具名插槽

案例5-12中,跟5-11的区别是,在子组件模板定义的时候,每个slot都有name值。在父组件引入子组件的时候,如何给对应的slot内容呢?每个slot插入的内容使用template标签,该标签使用v-slot:插槽名字,从而能够把对应的内容插入到对应的插槽中。注意: v-slot 只能添加在