1 组件化
1.1 什么组件化
如果我们将一个页面中所有的处理逻辑全部放在一起,处理起来就会变得非常复杂,而且不利于后续的管理以及扩展。但如果,我们把一个页面拆分成一个个小的功能块,每个功能块完成属于自己这部分独立的功能,那么之后整个页面的管理和维护就变得非常容易了。
组件化的思想:将一个完整的页面分成很多个组件。每个组件都用于实现页面的一个功能块。而每一个组件又可以进行细分。
1.2 Vue 组件化思想
Vue 提供了一种抽象,让我们可以开发出一个个独立可复用的小组件来构造我们的应用。任何的应用都会被抽象成一颗组件树。就如同下图。
1.3 注册组件的基本步骤
组件的使用分成三个步骤:1)创建组件构造器。2)注册组件,定义组件标签名 3)使用组件。
这里的步骤都代表什么含义呢?
1.Vue.extend()
调用 Vue.extend() 创建的是一个组件构造器。 通常在创建组件构造器时,传入 template 代表我们自定义组件的模板。该模板就是在使用到组件的地方,要显示的HTML代码。事实上,这种写法在Vue2.x的文档中几乎已经看不到了,之后会有语法糖版本
2.Vue.component()
调用 Vue.component() 是将刚才的组件构造器注册为一个组件,并且给它起一个组件的标签名称。所以需要传递两个参数:
注册组件的标签名
组件构造器
这里组件名使用驼峰命名法会报错,因为HTML 中的特性名是大小写不敏感的,所以浏览器会把所有大写字符解释为小写字符。所以组件名使用全小写 / kebab-case (短横线分隔命名)。
3.挂载组件到 Vue 实例上
2 认识组件
2.1 全局组件和局部组件
当我们通过调用 Vue.component()
注册组件时,组件的注册是全局的。这意味着该组件可以在任意Vue示例下使用。
1 | Vue.component('my-component-name', con) |
如果我们注册的组件是挂载在某个实例中, 那么就是一个局部组件,局部组件只能在绑定的标签内被识别。
1 | const vm = new Vue({ |
2.2 父组件和子组件
组件和组件之间存在层级关系,父子组件的关系是一种很重要的关系。
首先思考一个问题:子组件如何挂载到 Vue 实例中?
在父组件中注册子组件,然后在实例下注册父组件,最后把父组件挂载到 vue 实例中,可以让子组件的内容显示在页面中嘛?
答案是NO。我们还需要把子组件挂载到父组件上面。原因是:当子组件注册到父组件时,Vue 会编译好父组件的模块,该模板的内容已经决定了父组件将要渲染的HTML(相当于父组件中已经有了子组件中的内容了)。也就是<child-cpn></child-cpn>
是只能在父组件中被识别的,浏览器是无法直接识别它的。
子组件不仅要在父组件构造器中注册,还要挂载到父组件上面
1 | <div id="app"> |
2.3 注册组件语法糖
上面注册组件的方式看起来繁琐。Vue为了简化这个过程,提供了注册的语法糖。主要是省去了调用 Vue.extend() 的步骤,可以直接使用一个对象来代替。
1 | // 语法糖省略了 extends 直接用对象来代替创建组件构造器 |
2.3 模板的分离写法
模板分离写法能够将 template 模块中的 HTML 分离出来,然后挂载到对应的组件上。Vue 提供了两种方案来定义HTML模块内容:通过 id 把模板挂载到组件上
方法一:使用<script>
标签
1 | <script type="text/x-template" id="myCpn"> |
方法二:使用<template>
标签
1 | <template id="myCpn"> |
2.4 组件数据存放
组件不可以直接访问 Vue 实例数据,因为如果将所有的数据都放在 Vue 实例中,Vue 实例就会变的非常臃肿,因此组件应该有自己保存数据的地方。
组件数据的存放
组件对象也有一个 data 属性(也可以有methods等属性),但是这个 data 属性必须是一个函数,而且这个函数返回一个对象,对象内部保存着数据。下面是一个在模板中使用 data 的例子:
1 | <div id="app"> |
总结局部组件的写法
首先有一个 component 选项,里面包含的是对象,一个对象就是注册组件。对象的键值对中,键是组件名,值是一个对象,这个对象里也包含一系列属性,比如模板,数据,方法等。
为什么 data 在组件中必须是一个函数
首先,如果不是一个函数,Vue直接就会报错。其次,原因是在于 Vue 让每个组件对象都返回一个新的对象,因为如果是同一个对象的,多次使用同一个组件,组件们会共享数据,这显然不是我们想要的。下面就是用对象保存 data 数据,在 vue 实例上挂载 3 个一样的组件,修改一个组件的数据,其它两个跟着一起变化。

3 父子组件通信(传递数据)
子组件是不能直接引用父组件或者 Vue 实例的数据的,但是,在开发中,往往一些数据确实需要从上层传递到下层,如何进行父子组件间的通信呢?
- 通过 props 向子组件传递数据
- 通过 事件 向父组件发送消息

3.1 父级向子级传递 — props 基本用法
在组件中,使用 props 选项来声明需要从父级接收到的数据。props的值有两种方式:
- 字符串数组,数组中的字符串就是传递时的名称。
- 对象,对象可以设置传递时的类型,也可以设置默认值等。
1)字符串数组写法
下面是一个通过字符串数组的简单例子,vue 实例相当于父组件,实例中的局部组件就相当于子组件。
- 父组件数据: message
- 子组件数据: props中有一个 cpnmesg 的属性(这里属性名不需要和父组件的属性一样)
- 绑定数据:用 v-bind 把父组件的 message 绑定到子组件的 cpnmesg
- 子组件引用数据:
1 | <div id="app"> |
2)对象写法
当需要对 props 进行类型等验证时,就需要对象写法了,对父组件传入的数据进行类型验证:
验证所支持的数据类型有:String、Number、Boolean、Array、Object、Date、Function、Symbol
1 | props: { |
当我们有自定义构造函数时,验证也支持自定义的类型。

prop 的属性的驼峰标识问题
因为 html 不会识别大小写,如果 prop 属性的名字是驼峰命名法,在模板实例中绑定属性依然使用驼峰命名就会报错,此时我们需要把驼峰命名改成以 - 连接的字符。比如下面的 props 属性的 childMessage 在绑定模板的时候改写成 child-message ,但是在 template 里面仍然需要使用驼峰命名法 ,否则报错。
1 | <div id="app"> |
3.2 子级向父级传递
子组件向父组件传递数据,我们需要使用自定义事件来完成,v-on不仅仅可以用于监听DOM事件,也可以用于组件间的自定义事件。
自定义事件的流程:
在子组件中,通过$emit()来触发事件。
在父组件中,通过v-on来监听子组件事件。
下面是一个子组件向父组件传递的实例
1 | <!-- 1.父组件实例 --> |
*总结 *
在父组件有数据传给子组件的时候,在子组件中通过 props 选项 设置属性 propa ,然后把 propa 绑定到 父组件里的数据,从而获取父组件的数据。:child-message="message"
在子组件有数据要传给父组件时,子组件里面的数据发生变化,就需要向父组件发射改变后的数据,然后父组件通过绑定事件来接收数据。@dec="changeCount"
,这里也可以称之为监听子组件的数据。
*一个小案例 *
需求是:父组件向自组件传递 num1 和 num2,在子组件的输入框实现 num1 和 num2双向绑定,而且num 2 = num1 *100.

思路:
1.父传子,通过 props 把 num1 和 num2传给子组件
2.实现双向绑定,直接将子组件的 num1 和 num2 用 v-model 绑定是不建议的,因为子组件里num1 和 num2 是通过父组件来传递,如果又能通过 input 的输入值改变两个数的数值,这样会比较混乱。
因此推荐在子组件里定义 cnum1 和 cnum2 来代替 num1 和 num2,把cnum1 和 cnum2 绑定给 input 的value,完成 num1 和 num2 对输入框的 绑定。
然后在 input 中绑定监听事件,把每次输入框改变值付给 cnum1 和 cnum2 ,然后子组件向父组件传递数据。父组件自定义事件,监听 cnum1 和 cnum2 的变化,发生变化时子组件 emit 出 变化值给父组件,父组件里面的 method 将变化值付给 num1 和 num2 ,完成输入框对 num1 和 num2 的绑定
1 | changecount2(cnum1 ) { |
3.实现 num 2 = num1 ****100,在输入框设置监听 input 事件,当某一输入框触发事件,就改变另一输入框 value 所绑定的 cnum,并将数据改变 emit 出,让父组件监听到该事件,改变num1 和 num2的值。
1 | num2Input(event) { |
上面的方法可以通过 v-model + watch 实现双向绑定和监听
3.3 watch 选项
这里介绍一下 watch 选项
类型:{ [key: string]: string | Function | Object | Array }
watch 是一个对象,键是需要观察的表达式,值是对应回调函数。值也可以是方法名,或者包含选项的对象。Vue 实例将会在实例化时调用 $watch()
,遍历 watch 对象的每一个 property。
1 | watch: { |
键 pcount2 是监听发生变化的变量式子等,后面是一个回调函数,函数里面的参数 newValue 监听的变量的改变值。
4 组件间的访问方式(调用方法)
有时候我们需要父组件直接访问子组件,子组件直接访问父组件,或者是子组件访问根组件(Vue)。
- 父组件访问子组件:使用 $children 或 $refs reference (引用)
- 子组件访问父组件:使用 $parent
4.1 父子组件的访问方式: $children
this.$children
是一个数组类型,它包含所有子组件对象(在父组件下面使用可多个子组件)。
以根组件调用子组件为例
这里通过 vm(一个Vue 实例)访问他的第一个子组件的 say()方法
1 | vm.$children[0].say()//成功调用子组件中的say方法 |
遍历,获取所有子组件的 data 数据
1 | showChild() { |
$children
访问子组件时,是一个数组类型,访问其中的子组件必须通过索引值。但是当子组件过多,我们需要拿到其中一个时,往往不能确定它的索引值,甚至还可能会发生变化。当想明确获取其中一个特定的组件,这个时候就可以使用 $refs
4.2 父子组件的访问方式: $refs
$refs
和 ref
指令通常是一起使用的。
1.我们通过 ref
给某一个子组件绑定一个特定的 ID。
2.通过 this.$refs.ID
就可以访问到该组件了。
1 | <child-cpn1 ref="child1"></child-cpn1> |
注意事项: 官网解释不应通过 $refs
在父组件中绑定子组件的数据
关于 ref 注册时间的重要说明:因为 ref 本身是作为渲染结果被创建的,在初始渲染的时候你不能访问它们 - 它们还不存在!
$refs
也不是响应式的,因此你不应该试图用它在模板中做数据绑定。
4.3 父子组件的访问方式: $parent`` $root
在子组件中直接访问父组件,可以通过 $parent
,直接访问根组件用 $root
尽管在 Vue 开发中,允许通过
$parent
来访问父组件,但是在真实开发中尽量不要这样做。
子组件应该尽量避免直接访问父组件的数据,因为这样耦合度太高了。如果我们将子组件放在另外一个组件之内,很可能该父组件没有对应的属性,往往会引起问题。另外,更不好做的是通过$parent 直接修改父组件的状态,那么父组件中的状态将变得飘忽不定,很不利于我的调试和维护。
1 | components: { |
4.4 非父子组件通信
非父子组件关系包括多个层级的组件,也包括兄弟组件的关系。在 Vue1.x 的时候,可以通过 $dispatch
和 $broadcast
完成
$dispatch 用于向上级派发事件
$broadcast 用于向下级广播事件
但是在 Vue2.x 都被取消了在 Vue2.x 中,有一种方案是通过中央事件总线,也就是一个中介来完成。但是这种方案和直接使用 Vuex 的状态管理方案还是逊色很多。并且 Vuex 提供了更多好用的功能,详见 Vuex 的状态管理(后面再补链接)。
5 组件高级 — 插槽 slot
为什么要在组件使用插槽:
组件的插槽也是为了让我们封装的组件更加具有扩展性。
让使用者可以决定组件内部的一些内容到底展示什么。
比如下面这个例子,京东网站很多页面都会有导航栏,但是每个导航栏存在一些区别,但是他们的结构大体分为左中右三部分,我们可以通过插槽设置这三部分,这样就不用为每个导航栏都封装一个插件。

5.1 slot 的基本使用
首先我们对要使用 slot 的子组件进行分析 :抽取共性,保留不同。
然后进行封装:将共性抽取到组件中,将不同预留为插槽。
在子组件中,使用特殊的元素<slot>
就可以为子组件开启一个插槽。该插槽插入什么内容取决于父组件如何使用。
下面是一个简单的例子 ,我们可以在子组件模板的 slot 里面设置默认值(许多插槽是一致的时候使用),在实例中设置 slot 里面显示的内容
1 | <div id="app"> |
5.2 具名插槽 slot (已经被 v-slot 指令替代)
当子组件的功能复杂时,子组件的插槽可能并非是一个。比如我们封装一个导航栏的子组件,可能就需要三个插槽,分别代表左边、中间、右边。当我们只需要改变中间的插槽,怎么改变?
通过给模板中的 slot 定义一个 name 属性,然后在实例中给需要改动的部分设置为 slot="center"
就可以了。
1 | <body> |

**v-slot 新用法
上面部分已经被弃用了,修改的只是实例化组件的内容,模板 template 里面依然使用 slot 以及定义 name 属性。
1 | <div id="app"> |
5.3 作用域插槽
编译作用域
父组件模板的所有东西都会在父级作用域内编译;子组件模板的所有东西都会在子级作用域内编译。

以上面的代码为例,我们在使用<my-cpn v-show="isShow"></my-cpn>
的时候,整个组件的使用过程是相当于在父组件中出现的 , 因此这里使用的 isShow 是父组件的属性不是子组件的属性,值是 true。
作用域插槽的使用
作用域插槽的作用:父组件替换插槽的标签,但是内容由子组件来提供。也就是在子模板中使用了插槽,插槽里面提供数据,现在父组件想自定义数据的显示形式,这是就需要使用作用域插槽。
比如有这样一个需求,子组件里面有一组数组 langues: ['javaScript', 'python', 'java', 'c', 'c++']
,通过模板在父组件里面以不同的方式展示这组数据。

1 | <div id="app"> |
**v-slot 新写法
作用域插槽已经被 v-slot 替代,新的写法如下:
1 | <cpn> |
5.4 v-slot 指令
官网解释:
在 2.6.0 中,我们为具名插槽和作用域插槽引入了一个新的统一的语法 (即 v-slot 指令)。它取代了 slot 和 slot-scope 这两个目前已被废弃但未被移除且仍在文档中的 attribute。
v-slot 替换 slot 和 slot-scope 的写法参考上面的例子。