前端框架Vue自学之Vue组件化开发(三)

 终极目标:掌握和使用Vue(全家桶:Core+Vue-router+Vuex)

本博客目的:记录Vue学习的进度和心得(Vue组件化开发)

内容:通过官网说明,掌握Vue组件化开发。

正文:

Vue组件化开发

一、认识组件化

  1、什么是组件化?

  任何一个人处理信息的逻辑能力都是有限的,所以,当面对一个非常复杂的问题时,我们不太可能一次性搞定一大堆的内容。但是,我们人有一种天生的能力,就是将问题进行拆解,如果讲一个复杂的问题,拆分成很多个可以处理的小问题,再将其放在整体当中,你会发现大的问题也会迎刃而解。

  组件化也是类似思想:如果我们将一个页面中所有的处理逻辑全部放在一起,处理起来就会变得非常复杂,而且不利于后续的管理以及扩展。但如果,我们将一个页面拆分成一个个小的功能块,每个功能块完成属于自己这部分独立的功能,那么之后整个页面的管理和维护就变得非常容易了。

  2、Vue组件化思想

  组件化是Vue.js中的重要思想。它提供了一种抽象,让我们可以开发出一个个独立可复用的小组件来构造我们的应用,任何的应用都会被抽象成一颗组件树

  3、组件化思想的应用

  有了组件化的思想,我们在之后得开发中就要充分地利用它。尽可能地将页面拆分成一个个小的、可复用的组件。这样让我们的代码更加方便组织和管理,并且扩展性也更强。(避免大量重复代码)

二、组件化基础之注册组件

  组件的使用分为三个步骤:创建组件构造器对象(调用Vue.extend()方法),注册组件(调用Vue.component()方法),使用组件(在Vue实例的作用范围内 )。

  1、创建组件构造器对象(通常在最外层嵌套一层div)

  例如:const cpnC = Vue.extend({ template:`<div><h2>XX标题</h2></dix>`}) 注意,template是模板的意思,里面用的是``(tab键上的波浪线键,ES6语法,可以用于字符串,有能自动换行的效果),该模板就是在使用到组件的地方,要显示的HTML代码。这种写法在Vue2.x文档没了,会使用一个语法糖的写法。

  2、注册组件(全局组件)

  Vue.component('组件名',附件构造器对象)

  3、在Vue实例管理的HTML元素范围内调用组件(只有管理到的HTML,例如Vue实例通过id选择器选中下面这个div块)

  <div id='app'><组件名><组件名></div> 

  4、全局组件与局部组件

  全局组件意味着可以在多个vue实例下面使用。局部组件就是在Vue实例定义下,注册组件,例如在vue的components属性对象中定义{cpn: cpnC},其中cpn是组件名,cpnC是组件构造器对象。在开发中,使用比较多的是局部组件。

  5、父组件与子组件

  在组件2中,使用components属性定义以组件1的构造器注册组件1,也就是在组件2中,注册组件1,此时,组件2中的构建器对象中可以调用组件1(因为在其内部已经注册),接着再Vue实例中注册组件2(Vue的components属性内),这个时候就可以在Vue实例管理的HTML元素中,调用组件2,组件2里面还包含着组件1。所以组件2就是父组件,组件1就是子组件。(其实,Vue实例也是一个组件(最顶层组件,root组件),也有template属性可以定义。)注意,在Vue实例管理的HTML元素中调用组件1是没有效果的,因为其没有在Vue实例中注册,如果想用,应该在Vue实例中注册组件1。(这也可以理解为什么任何一个应用都能用组件树表示了。)

  6、注册组件的语法糖写法(内部还是会调用Vue.extend方法) 

  就是把原来在组件构造器中的Vue.extend()中的对象{template:XXX},直接放入Vue.component('组件名',{template:xxx})。注册局部组件也是一样的,把对象{template:XXX}直接放入Vue实例的components中{‘组件名’:{template:xxx}}。

  7、组件模板抽离的写法(后面用cli脚手架时,会有更好的写法)

  第一种写法:在外部写一个script标签并定义text/x-template类型并绑定id,然后在里面写模板HTML信息,如<script type ='text/x-template' id='cpn'><div><h2></h2></div></script>。然后在注册组件中的构造器中用id选择器选中这个模板,如Vue.component('组件名1',{template: ‘#cpn’}),接着再使用即可。

  第二种写法:使用template标签,并且绑定id。例如,<template id='cpn'>div><h2></h2></div></template> 。后面也是注册和使用。(推荐第二种)

二、组件化基础之数据传递(组件通信)

  1、为什么组件data必须是函数?(其实组件的原型就是一个Vue实例,Vue实例有的,组件也有,即data属性,methods)

  组件内部是不能直接访问vue实例内部的data属性的。 组件是一个单独功能模块的封装,这个模块有属于自己的HTML模板,也有属于自己的数据data。通过在组件构造器对象中的data属性方法(注意是方法,而不是属性对象),data方法要返回一个对象,该返回对象内部中,定义我们的具体的data。例如,<template id='cpn'>div><h2>{{title}}</h2></div></template>,而在Vue.component('组件名1',{template: ‘#cpn’, data() { return {title: 'cba'}}}),这样就可以把模板的title用组件的data渲染出来了,为‘cba’。

  组件里面有着自己的作用域(封装),当组件想要有某些功能的时候,应该在组件内部定义methods方法。 由于组件应该是独立的(为了更好的复用),如果之前在组件内部定义的data是一个属性对象,那么当使用多个同一组件是,就会共享这个data属性对象,导致,在一个组件修改data时(相当于修改同一个对象),会影响另一个组件的data。所以data属性应该使用方法返回一个大的对象(记得要用大括号包括,即栈空间,能够为return返回的对象创建多个变量每次返回新的对象(新的地址),不然直接传入对象的话,就可以属性对象的是同一种情况了,即指向同一个内存地址了),对象里面包含要用的数据,使得在组件中的数据修改不会影响别的同一组件。

  2、父子组件通信之父传子

  之前我们提到,子组件是不能引用父组件或者Vue实例的数据的,但是,在开发中,往往一些数据确实需要从上层传递到下层,例如,一般数据获取是通过外层组件(父组件)向服务器发送请求,获取相关数据,不会让子组件再次发送一个网络请求(这样对服务器压力很大),而是由父组件传数据给内部子组件(遍历)进行展示渲染,这时候就涉及到父子组件通信的问题。

  Vue官方提到了两种方式:通过props向子组件传递数据(父传子)和通过事件向父组件发送消息($emit event)(子传父)。

  在真实开发中,Vue实例和子组件的通信和父组件和子组件的通信过程是一样的。因为Vue实例就是一个根组件(父组件)。

  即当我们要使用vue实例下面的data数据(变量2)时,通过在子组件定义props属性(可以是对象、字符串数组),格式为props:{变量1},然后在使用这个子组件时,用v-bind绑定变量1到变量2上,此时就可以在子组件使用到data数据了,注意是调用变量1(其是调用变量2)。

  除了数组之外,我们也可以使用是对象,当需要对props进行类型等验证时,就需要对象写法了。类似于:props:{cmessage: String}。可以是多个类型。或者可以提供一些默认值,类似于:props:{cmessage: {type: String, default:'abc'}}。类型是对象或者数组时,默认值必须是一个函数(通过定义函数返回对象或者数组等)。里面还可以有required属性,当设置为true时,就会要求必须要外部传入这个变量(必传值)。注意,如果是在子组件内的props设置了一些默认值,当通过v-bind绑定有重复的变量的时候,就会把默认值覆盖。当然,也可以设置自己的自定义验证类型。

  父传子props中的驼峰标识。通常我们在写JS时,习惯使用驼峰标识。但是在v-bind后面是不支持驼峰标识识别的,必须把驼峰的地方用-连接,并且后面字符是小写。例如在props定义了cInfo,那么在v-bind后面用c-info。

  在子组件定义模板时(<template>),里面必须要有一个根元素,所以一般里面用div包裹起来。例如<template><h2>{{mes1}}</h2><h2>{{mes2}}</h2></template>,这样会报错,因为没有根元素,要改成<template><div><h2>{{mes1}}</h2><h2>{{mes2}}</h2></div></template>,这样就不会报错了。

  3、父子组件通信之子传父(自定义事件)

  在实际开发中,经常会发生,当子组件发生一些事件时,父组件要知道子组件发生了什么事并且是得知道那个子组件发生了事件,从而可以进行后续的一些处理。这时候就是子组件与父组件通信的问题了。

  通过在使用子组件的时候使用v-on监听事件,然后在子组件的methods里面定义方法,实现相关功能。但是注意,只是这样的话,是子组件监听子组件发生的事件。

  我们希望是子组件告诉父组件发生的事件,在子组件的methods里面定义方法是有要求的, 得使用this.$emit('事件1',参数)方式发送事件(这个是自定义事件,可以带参数,这个参数是默认发送的参数,像之前如果没有写参数,是默认的发送event事件),然后在调用父组件中调用的子组件中,用v-on监听子组件的自定义事件(之前v-on用的都是常规的DOM事件,如click等,而这里是我们自定义的事件),类似v-on:事件1=‘事件2’,然后在父组件中的methods里面定义我们的事件2并且它是可以接收到参数的。这样就完成了子传父的通信。

  4、在定义Vue组件时,一般封装为.vue文件。里面包含三大块:<template></template>(写模代码),<script></script >(写JS代码)和<style></style>(写样式)。(tips:当组件被编译后,是没有模板<template>的,只有渲染函数render。)

  5、当我们需要在子组件双向绑定Vue实例data数据(和input标签)时,官方推荐,不要直接绑定props中的数据,而是v-bind绑定在子组件的data方法下返回定义的数据(可以使返回的数据指向props的数据),然后在子组件中v-on监听事件,并且在子组件定义methods方法,通过定义传入event(默认下)的函数,并把子组件内的事件和值用$emit传出去,接着再使用子组件的地方监听这个自定义事件,最后在Vue实例(父组件)的methods定义这个方法。(其实这就是v-model的本质,综合应用v-bind和v-on,实现双向绑定)

  6、watch。

  组件内也存在watch监听属性,能够监听某些变量的改变,并且会缓存两个值:改变前的变量值和改变后的变量值,watch内可以定义相关函数处理。

  7、父子组件的访问方式(通过访问对象的方法)

  有时候我们需要父组件直接访问子组件,子组件直接访问父组件或者是子附件访问根组件。

  父组件访问子组件:使用$children或$refs。这些都是对象,里面包含子组件的很多属性和方法。(reference引用的意思)$children会把父组件中的所有子组件作为一个对象,然后根据索引去控制使用哪个子组件。开发中,一般不用$children(因为索引会因为在中间插入别的组件时,不稳定会变化,导致取错子组件),而是使用$refs。 $refs的使用要求我们在想控制的子组件的属性加上ref引用属性,例如ref='abc',然后通过$refs进行准确调用子组件对象了,例如this.$refs.aaa,就是刚刚那个子组件对象了并且与顺序索引无关。

  子组件访问父组件:使用$parent。$root(访问根组件)。在子组件内使用$parent,可以获取父组件对象。(开发里面用的比较少,因为这样的操作会使得子组件不独立了,子与父的耦合度太高了。)

  

三、组件化高级之插槽slot  

  1、为什么使用slot?

  组件的插槽是为了我们封装的组件更加具有扩展性,是让使用者可以决定组件内部的一些内容到底展示什么。例如在电商软件中,每个页面存在一个导航栏,我们可以看做一个nav-bar组件,但是会根据不同的状态显示不同的导航栏,如果我们想都应用这个组件,就可以通过加入插槽,通过根据不同的状态放入不同的扩展组件,实现同一个nav-bar组件的复用(提供一个大的相同结构,但是内容可以不一样(扩展组件))。

  2、封装与插槽

  抽取共性,保留不同。最好的封装方式就是将共性抽取到组件中,将不同暴露为插槽。一旦我们预留了插槽,就可以让 使用者根据自己的需求,决定插槽中插入什么内容。

  3、slot基本使用

  在组件模板<template>元素中添加插槽<slot></slot>,作为组件预留扩展位置。然后在具体使用组件的时候,在内部把我们想用的组件(会当做一个整体)放进去(相当于对应于之前定义插槽位置),例如简单就放入一个button组件或者是一段HTML都行。 插槽里面可以定义默认值,当没有在使用组件时的其内部使用扩展组件,就会以默认的情况显示。

  3、具名插槽slot

  当我们在一个组件中使用了多个插槽,但是我们只想在使用组件的时候只改变某一个插槽(或者是插入别的组件),这时如果没有在模板的插槽中使用name属性,其会替换所有没有使用到name的插槽。所以我们应该对每个插槽使用name属性,然后在使用组件的时候,在要插入的元素中使用slot属性,让其等于要改变的插槽的name,指定该插槽。

  4、编译作用域

  变量编译的作用域是取决于在哪个模板内使用的变量。例如在vue实例定义了一个isActive变量为true,而在vue实例下的一个组件内定义了一个isActive变量为false。当在Vue实例下的使用这个组件,里面用了isActive变量,其值是为true,因为该变量是在Vue实例管理的模板里的。如果是在这个组件内的模板使用了isActive变量,则是为false,道理一样。(官方给出了一条准则:父组件模板的所有东西都会在父级作用域内编译;子组件模板的所有东西都会在子级作用域内编译。)

  5、作用域插槽

  父组件替换插槽的标签,但是内容由子组件来提供。类似于数据内容是由子组件提供,但是父组件在使用子组件的时候,需要一些改变,例如改变原来在子组件默认的显示方式,变为自己可以定义的方式去显示。此时,就可以把子组件的对应内容先用slot插槽包裹并且通过在这个slot内v-bind绑定子组件的数据(如<slot :data='子组件数据'>),然后在父组件内使用到的子组件中定义一个模板(<template>或者HTML元素(2.X版本)),并且使用到slot-scope属性(相当于对之前在子组件的定义slot内容)(如<template slot-scope='slot1'>),由于之前slot内v-bind绑定了子组件的数据,这个时候就可以在这个模板(<template>或者HTML元素(2.X版本))内通过使用slot-scope属性的值(如slot1.data),就可以使用子组件的数据data了。外部父组件可以利用子组件提供的数据进行一些应用。

四、组件化高级之动态组件

五、组件化高级之异步组件

六、组件声明周期