资讯专栏INFORMATION COLUMN

后端开发者的Vue学习之路(三)

番茄西红柿 / 3020人阅读

摘要:使用组件全局定义组件,第一个参数是组件名,的值是组件的内容这是个待办项实例化是必须的,要把使用组件的区域交给管理局部注册组件局部注册组件全局注册往往是不够理想的。

目录

  • 上节内容回顾
  • 组件
    • 什么是组件
    • 组件注册
      • 全局注册组件
      • 局部注册组件
    • 使用细节
      • 组件注册的命名规范:
      • 组件中只有一个根元素
      • 组件也是一个实例
      • 组件在某些元素中渲染出错
  • 组件间的数据传递
    • 父子组件传递数据
    • 子组件向父组件传输数据
    • 非父子组件之间的传值
    • 单向数据流
  • Props属性
    • 命名规范:
      • 大小写问题
    • 参数校验
      • 限制props的类型
      • 设置默认值
      • 要求数据必传
      • 自定义验证函数:
    • 传递静态或动态Prop
    • 补充:
  • 给组件绑定原生的事件
  • template
    • 在template上使用v-if
    • 使用v-for
  • 插槽
    • 通过插槽分发内容
      • 具名插槽
      • 插槽的默认内容
    • 作用域插槽
  • 动态组件
    • is
    • keep-alive
    • 补充
  • $refs
    • 使用步骤:
    • 获取组件的引用
  • 动画效果

首发日期:2019-01-26


上节内容回顾

  • 数据绑定:v-model
  • 样式绑定:v-bind:class,v-bind:style
  • 事件:v-on
  • Vue指令
  • 数组操作(知道哪些数组操作是能被vm层监听到并能响应式更新到视图上的)
  • Vue的元素复用问题(不使用key时会尽量复用)

组件


【官方的话】组件系统是 Vue 的另一个重要概念,因为它是一种抽象,允许我们使用小型、独立和通常可复用的组件构建大型应用。仔细想想,几乎任意类型的应用界面都可以抽象为一个组件树:

小菜鸟的话:定义组件就好像定义了一堆“带名字”的模板,比如说可能会有叫做“顶部菜单栏”的组件,我们可以多次复用这个“顶部菜单栏”而省去了大量重复的代码。


什么是组件

  • 在以前的多页面开发的时候,我们可能会经常需要一个“顶部菜单栏”,于是我们在每个html文件中都要加上关于“顶部菜单栏”的代码。可能你会想这份代码能够“复用”就好了。而组件可以定义模板,从而达到复用代码的作用。
  • 组件可以大大提高我们构建页面的效率。
  • 你可以将组件进行任意次数的复用
    下面用代码来演示一下"复用效果":
    
      

代码效果:


组件注册

组件注册就是“定义模板”,只有注册了的组件,Vue才能够了解怎么渲染。


全局注册组件

  • 全局注册的组件可以用在其被注册之后的任何 (通过 new Vue) 新创建的 Vue 实例中,也包括其组件树中的所有子组件的模板中。【一个Vue应用只有一个根实例,但还允许有其他的实例。在 Vue 里,一个组件本质上是一个拥有预定义选项的一个 Vue 实例。】
  • 全局注册的行为必须在根 Vue 实例 (通过 new Vue) 创建之前发生
  • 全局注册的组件可以在另一个组件中使用。


    
        
        
    

    
        


局部注册组件


全局注册往往是不够理想的。比如,如果你使用一个像 webpack 这样的构建系统,全局注册所有的组件意味着即便你已经不再使用一个组件了,它仍然会被包含在你最终的构建结果中。这造成了用户下载的 JavaScript 的无谓的增加。

在这些情况下,你可以通过一个普通的 JavaScript 对象来定义组件:

    
      


上面的全局注册说了允许在组件中使用其他组件,但注意局部注册的组件要声明使用其他组件才能够嵌套其他组件。例如,如果你希望 ComponentA 在 ComponentB 中可用,则你需要这样写:

    
      


使用细节


组件注册的命名规范:

组件名可以使用类my-component-name(kebab-case (短横线分隔命名))或MyComponentName的格式(PascalCase 首字母大写命名法),使用组件的时候可以,但在有些时候首字母大写命名法定义组件的是不行的,所以通常推荐使用【当你使用首字母大写命名法来定义组件的时候,不能直接在body中直接写组件名,而要求写在template中,如下例】。


    
      


组件中只有一个根元素

每个组件必须只有一个根元素!!
所以下面是不合法的:

如果你确实要有多个元素,那么要有一个根元素包裹它们:


组件也是一个实例


组件也是一个实例,所以组件也可以定义我们之前在根实例中定义的内容:data,methods,created,components等等。
但一个组件的 data 选项必须是一个函数,因此每个实例可以维护一份被返回对象的独立的拷贝


组件在某些元素中渲染出错

在一些html元素中只允许某些元素的存在,例如tbody元素中要求有tr,而不可以有其他的元素(有的话会被提到外面)。下面是一个元素被提到外面的例子【而ul并没有太严格,所以我们在前面的todo-list的例子中能够演示成功】

下图可以看的出来div被提到table外面了:

这是为什么呢?目前来说,我们在页面中其实是先经过html渲染再经过vue渲染的(后面项目话后是整体渲染成功再展示的),当html渲染时,它就发现了tr里面有一个“非法元素”,所以它就把我们自定义的组件提到了table外面。
解决方案:
使用tr元素,元素里面有属性is,is的值是我们要使用的组件名

    
        



但不会在一下情况中出错:

  1. 定义组件的时候,template中包含自定义的组件
  2. 单文件组件,也就是说引用vue文件来注册一个组件的时候(这个东西会在后面讲)。
  3. 代码效果:很明显的,我们的值成功传给子组件了。


    子组件向父组件传输数据

    • 我们可以在子组件中使用emit来触发事件,然后在使用这个组件的时候绑定这个事件就可以监听到这个事件的发生(这时候调用的函数是父组件的处理函数),从而使得父组件接受到子组件传递的消息了。
      要给父组件传递数据主要有两个步骤
    1. 在定义组件时,定义一个包含触发事件的元素,这个事件触发时将会调用emit来触发事件【例如可以在按钮上定义一个onclick事件,这个onclick事件触发时将会调用emit】
    2. $emit()可以有多个参数,第一个参数是触发的事件的名称,后面的参数都是随着这个事件向外抛出的参数。
    3. 使用组件时,对组件进行事件监听,监听的事件与组件内抛出的事件一致
    4. 定义处理目标事件的函数,函数的参数是随事件向外抛出的多个参数。


    演示代码如下:

        
            

    【小tips:上面有多重字符串的使用,普通的双引号和单引号已经不足以嵌套使用了,在外层可以使用反引号` ` `【esc下面那个键】来包裹,它也可以达到字符串包裹的效果,特别的是它支持多行字符串。】


    非父子组件之间的传值

    祖孙组件传数据、兄弟组件传数据都属于非父子组件之间的传值。

    1. 【如果是祖孙组件传数据,可以使用父组件传给子组件,子组件传给孙组件。但这是一个费事的做法。】
    2. 一般都会使用vuex,vuex就像一个变量存储器,我们可以把一些多个组件都需要用到数据存储到vuex的store中。【这个由于内容较重要,留到后面再讲】
    3. 只有少量组件使用某个数据的时候也可以使用bus模式,bus相当于给每一个组件都加上“同一个”新的vue实例,由于bus是实例之间共享的,当数据发生改变时,可以利用这个vue实例来调用emit方法来抛出新值,而其他组件监听bus中的事件就可以获取到新的值,这样就实现了非父子组件之间的传值。


    使用bus传输数据的步骤:

    1. 在Vue的原型上定义vue:Vue.prototype.bus = new Vue()
    2. 当数据发生变化时,调用emit:this.bus.$emit(change,当前组件的数据)
    3. 在组件上监听bus的事件:this.bus.$on(change,一个用于赋值的函数)
    4. 在函数中获取事件触发带过来的参数,赋给当前组件,从而实现两边数据同步。

    下面的代码是点击某个组件发生数据变化时,另一个组件的数据也发生变化:

        
            


    单向数据流

    • 单向数据流:props使得父组件的数据能够传输到子组件,而且传输的源数据发生改变时,子组件也会发生改变。但如果在子组件中更改prop,这是不行的,会报警告。
    • 每次父级组件发生更新时,子组件中所有的 prop 都将会刷新为最新的值。如果允许你在子组件中修改父组件传入的数据的话,使用了父组件的这个数据的所有子组件的数据都会被修改(这样就降低了组件的复用效果,导致数据流行不确定,难以确定这个数据是在哪个组件中修改的,而且是一个相对危险的行为)
    • 你不应该在一个子组件内部改变 prop。如果你这样做了,Vue 会在浏览器的控制台中发出警告。【这就是单向数据流】
    • 如果你确实需要修改:
      • 那么你应该创建一个子组件内部的数据,这个内部数据由传入的prop的数据进行初始化。(也就是进行数据拷贝)
      • 又或者你可以使用计算属性。



    Props属性


    命名规范:

    大小写问题

    【有个建议,建议写属性名的时候都使用kebab-case (短横线分隔命名) 命名,因为这个的兼容效果最好】
    HTML 中的特性名是大小写不敏感的,所以浏览器会把所有大写字符解释为小写字符。如果你在props中使用了驼峰命名法,那你在定义属性的时候需要使用kebab-case (短横线分隔命名) 命名才能正确传输数据【因为短横线后面的字符可以识别成大写,从而能够匹配到】。

    如果在属性中也使用驼峰命名法命名属性的时候会报这样的错:Prop "mycontent" is passed to component , but the declared prop name is "myContent". Note that HTML attributes are case-insensitive and camelCased props need to use their kebab-case equivalents when using in-DOM templates. You should probably use "my-content" instead of "myContent"

        
            

    同样的,如果在组件的template属性中使用驼峰命名法的属性,那么这个限制就不存在了。


    参数校验

    限制props的类型

    有时候需要使用第三方的组件的时候,所以会需要传输数据给这个组件来渲染。但如何限制传入的数据的类型呢?

    • 可用于限制数据类型的类型:
      • String:字符串
      • Number:数字
      • Boolean:布尔值
      • Array:数组
      • Object:对象
      • Date:日期
      • Function
      • Symbol


    格式:

    props: {
    // 数据名:数据类型
      title: String,
      likes: Number,
      ...
    }


    如果传入的类型不对,那么会报Invalid prop: type check failed for prop "xxx". Expected String with value "xxx", got Number with value xxx.的错误。


    如果允许多种值,可以定义一个数组:

    props: {
      content: [String,Number]
    }


    设置默认值

    我们也可以给props中的数据设置默认值,如果使用default设置值,那么没有传某个数据时默认使用这个数据。

    props: {
      content: {
      type:[String,Number],
      default:'我的默认值'
      }
    }

    如果使用default给数组或对象类型的数据赋默认值,那么要定义成一个函数。


    要求数据必传

    如果要求某个数据必须传给子组件,那么可以为它设置required。
    格式:

    props: {
        content: {
           type: String,
           required: true
       }
    }

    如果没传,会报Missing required prop: "xxx"的错。


    自定义验证函数:

    如果想要更精确的校验,可以使用validator,里面是一个函数,函数的第一个参数就是传入的值,当函数内返回true时,这个值才会校验通过。
    以下的代码是要求传入的字符串长度大于6位的校验:

            Vue.component('child', {
              props: {
                content: {
                    type: String,
                    validator: function(value) {
                        return (value.length > 6)
                    }
                }
              }

    如果验证不通过,会报Invalid prop: custom validator check failed for prop "xxx"的错。



    传递静态或动态Prop

    • 传递静态或动态的prop意思就是传递的是一个常量还是变量。
    • 传递常量的时候:
      • 如果是字符串,可以不使用v-bind。
      • 如果是数字,布尔值,数组,对象,那么需要使用vue的v-bind来设置属性,因为使用普通的属性设置会被认为是字符串,使用v-bind的时候会被认为是js表达式(从而成功识别成正确的类型)。
    • 传递变量的时候都要使用v-bind,不然无法识别成一个变量。


    补充:

    • 没有讲的内容:非 Prop 的特性



    给组件绑定原生的事件


    用下面的代码来说一个问题:

    
    
        
        demo
    
        
          


    上面的代码你会发现点击了按钮却没有调用函数。
    而下面的按钮按了会打出child。

    
    
        
        demo
    
        
          


    • 在上面我们提过了父子数据传递,我们知道了父组件的数据需要props来传递给子组件,说明了父组件的数据是不跟子组件共享的。
    • 而事件也是这样的,在我们使用组件的时候,并不能直接监听一些事件的发生。【例如上面的子组件传数据给父组件也需要使用emit。】
    • 对于父组件来说,这个组件是子组件下的按钮,所以直接点击这个按钮并不会触发事件【或者说你可以认为点击了事件了,但是没有emit出来,所以父组件监听不到】。
    • 而如果希望点击按钮的时候能够触发出按钮的原生事件(不把它当作子组件下的按钮),那么需要把它绑定成原生事件。我们可以使用.native来修饰事件来说明监听的是一个原生事件。


    下面的代码是使用了emit来达到同样效果的代码:

    
    
        
        demo
    
        
          



    template

    • template是vue中的一个元素。它是一个不会渲染到页面的元素,通常地它有如下几个用处:
      • 由于不会被渲染处理,可以在template上使用使用v-if,把一个