6.组件 ★

1.组件(Component)是Vue.js最核心的功能。
2.组件的注册有全局注册和局部注册两种方式。全局注册后,任何Vue示例都可以使用。
全局注册示例:

Vue.component('my-component',{
  //选项
})

my-component 就是注册的组件自定义标签名称,推荐使用小写加减号分割的形式命名。
要在富实例中使用这个组件,必须要在示例创建前注册,之后就可以用的形式来使用组件了,示例:

template的DOM结构必须被一个元素饱含,如果直接写内容,不带

是无法渲染的。
在Vue实例中,使用components选项可以局部注册组件,注册后的组件只有在该实例作用域下有效。组件中可以使用components选项来注册组件,使组件可以嵌套。示例:

3.Vue组件的模版在某些情况下会收到HTML的限制,比如

内规定只允许是、
等这些表格元素,所以在内直接使用组件是无效的。在这种情况下,可以使用 特殊的is属性 来挂载组件,示例:

tbody在渲染时,会被替换为组件的内容。常见的限制元素还有

      这里用v-model绑定了父级的数据parentMessage,当通过输入框任意输入时,子组件接收到的props “message” 也会实时响应,并更新组件模版。
      注意,如果要直接传递数字、布尔值、数组、对象,而且不使用v-bind,传递的仅仅是字符串,尝试下面的示例来对比:

      同一个组件使用了两次,区别是第二次使用的是v-bind。渲染后的结果是,第一个是7,第二个是才是数组的长度3。
      10.Vue2.x通过props传递数据是单向的。也就是父组件数据变化时会传递给子组件,但是反过来步行。
      11.业务中经常会遇到两种需要改变prop的情况,一种是父组件传递初始值近来,子组件将它作为初始值保存起来,在自己的作用域下可以随意使用和修改。这种情况可以在组件data内再声明一个数据,引用父组件的prop,示例代码:

      组件中声明了数据count,它在组件初始化时会获取来自父组件的initCount,之后就与之无关了,只用维护count,这样就可以避免直接操作initCount。
      另一种情况就是prop作为需要被转变的原始值传入。这种情况用计算属性就可以了,例如:

      因为CSS传递宽度要带单位(px),但是每次都写太麻烦,而且数值计算一般是不带单位的,所以统一在组件内使用计算属性就可以了。
      注意,在JavaScript中对象和数组是引用类型,指向同一个内存空间,所以props是对象和数组时,在子组件内改变是会影响父组件的。
      12.上面介绍的props选项的值都是一个数组,除了数组外,还可以是对象,当prop需要验证时,就需要对象写法。
      一般当你的组件需要提供给别人使用时,推荐都进行数据验证,比如某个数据必须是数字类型,如果传入字符串,就会在控制台弹出警告,以下是几个prop的示例:

       Vue.component('my-component',{
              props:{
                  //必须是数字类型
                  propA:Number,
                  //必须是字符串或数字类型
                  propB:[String,Number],
                  //布尔值,如果没有定义,默认值就是true
                  propC:{
                      type:Boolean,
                      default:true
                  },
                  //数字,而且是必传
                  propD:{
                      type:Number,
                      required:true
                  },
                  //如果是数组或对象,默认值必须是一个函数来返回
                  propE:{
                      type:Array,
                      default:function(){
                          return [];
                      }
                  },
                  //自定义一个验证函数
                  propF:{
                      validator:function(value){
                          return value > 10;
                      }
                  }
              }
      

      验证的type类型可以是:

      • String
      • Number
      • Boolean
      • Object
      • Array
      • Function
        type也可以是一个自定义构造器,使用instanceof检测。
        当prop验证失败时,在开发版本下回在控制台抛出一条警告。
        13.Vue组件的通信场景:父子组件通信、兄弟组件通信、跨级组件通信。
        14.当子组件需要向父组件传递数据时,就要用到自定义事件。v-on除了监听DOM事件外,还可以用于组件之间的自定义事件。
        15.与JavaScript的设计模式——观察者模式中的dispatchEvent和addEventListener这两个方法类似。Vue组件也有一套模式,子组件用$emit()来触发时间,父组件用$on()来监听子组件的事件。
        父组件也可以直接在子组件的自定义标签上使用v-on来监听子组件触发的自定义事件,示例代码:

      总数:{{total}}

      上面的例子中,子组件有两个按钮,分别实现加1和减1的效果,在改变组件的data "counter"后,通过$emit()再把它传给父组件,父组件用v-on:increase 和 v-on:reduce (示例使用的是语法糖)。$emit方法的第一个参数是自定义事件的名称,例如示例的increase和reduce后面的参数都是要传递的数据,可以不填或填写多个。
      16.除了用 v-on 在组件上监听自定义事件外,也可以监听DOM事件,这时可以用.native 修饰符表示监听的是一个原生事件,监听的是该组件的根元素,示例如下:

      
      

      17.使用 v-model
      Vue2.x可以在自定义组件上使用 v-model指令,示例:

      总数:{{total}}

      仍然是点击按钮加1的效果,不过这次组件$emit()的事件名是特殊的input,在使用组件的父级,并没有在上使用@input="handler",而是直接用了v-model绑定的一个数据total。这也可以称作是一个语法糖,因为上面的示例可以间接地用自定义事件来实现:

      总数:{{total}}

      v-model还可以用来创建自定义的表单输入组件,进行数据双向绑定,例如:

      总数:{{total}}

      实现这样一个具有双向绑定的v-model组件要满足下面两个要求:

      • 接收一个value属性
      • 在有新的value时触发input事件
        18.在Vue.js 2.x中,推荐使用一个空的Vue实例作为中央事件总线(bus),也就是一个中介。示例代码:
      {{ message }}

      首先创建了一个名为bus的空Vue实例。然后全局定义了组件component-a;最后创建Vue实例app,在app初始化时,也就是在生命周期mounted钩子函数里监听了来自bus的事件on-message,而在组件component-a中,点击按钮会通过bus把事件on-message发出去,此时app就会接收到来自bus的事件,进而在回调里完成自己的业务逻辑。
      这种方法巧妙而轻量地实现了任何组件间的通信,包括父子、兄弟、跨级。如果深入使用,可以扩展bus实例,给它添加data、methods、computed等选项,这些都是可以公用的,在业务中,尤其是协同开发时非常有用,因为经常需要共享一些通用的信息,比如用户登录的昵称、性别、邮箱等,还有用户的授权token等。只需在初始化时让bus获取一次,任何时间,任何组件就可以从中直接使用了,在单页面富应用(SPA)中会很实用。项目比较大时,可以选择更好的状态惯例解决方案vuex。
      19.除了中央事件总线bus外,还有两种方法可以实现组件间通信:父链 和子组件索引。
      20.在子组件中,使用this.$parent 可以直接访问该组件的父实例或组件,父组件也可以通过this.$children 访问它的所有的子组件,而且可以递归向上或向下无限访问,直到根实例或最内层的组件。示例:

      {{ message }}

      尽管Vue允许这样操作,但在业务中,子组件应该尽可能地避免依赖父组件的数据,更不应该去主动修改它的数据,因为这样使得父组件紧耦合,只看父组件,很难理解父组件的状态,因为它可能被任意组件修改,理想情况下,只有组件自己能修改它的状态。父子组件最好还是通过props和$emit来通信。
      21.当子组件较多时,通过this.$children 来一一遍历出我们需要的一个组件实例是比较困难的,尤其是组件动态渲染时,它们的序列是不固定的。
      Vue提供了子组件索引的方法,用特殊的属性ref来为子组件指定一个索引名称,示例:

      在父组件模版中,子组件标签上使用ref指定一个名称,并在父组件内通过this.$refs 来访问指定名称的子组件。$refs只在组件渲染完成后才填充,并且它是非响应式的。它仅仅作为一个直接访问子组件的应急方案,应当避免在模板或计算属性中使用$refs。
      22.当需要让组件组合使用,混合父组件的内容和子组件的模板时,就会用到slot,这个过程叫作内容分发(transclusion)。以为例,它有两个特点:

      • 组件不知道它的挂载点会有什么内容。挂载点是由的父组件决定的。
      • 组件很可能有它自己的模板。
        props传递数据、events触发事件和slot内容分发就构成了Vue组件的3个API来源,再复杂的组件也是由这3部分构成的。
      1. slot。如下图,一个比较常规的网站布局:


        image.png

      这个网站由一级导航、二级导航、左侧列表、正文以及底部版权信息5个模块组成,如果要将它们都组件化,这个结构可能会是:

      
          
          
          

      当需要让组件组合使用,混合父组件的内容与子组件的模板时,就会用到slot,这个过程叫做内容分发(transclusion)。
      为例,它有两个特点:

      • 组件不知道它的挂载点会有什么内容。挂载点的内容是由的父组件决定的。
      • 组件很可能有它自己的模板
        props传递数据、events触发事件 和 slot 内容分发就构成了Vue组件的3个API来源,再复杂的组件也是由这3部分构成的。
        编译的作用域。比如父组件有如下模板:
      
        {{ message }}
      
      

      这里的message就是一个slot,但是它绑定的是父组件的数据,而不是组件的数据。
      父组件模板的内容是在父组件作用域内编译,子组件模板的内容是在子组件作用域内编译。例如下面的示例:

      这里的状态showChild绑定的是父组件的数据,如果想在子组件上绑定,那应该是:

      因此,slot分发的内容,作用域是在父组件上的。
      单个slot。在子组件内使用特殊的元素就可以为这个子组件开启一个slot(插槽),在父组件模板里,插入在子组件标签内的所有内容将替代子组件的标签及它的内容。示例代码:

      分发的内容

      更多分发的内容

      子组件child-component的模板内定义了一个元素,并且用一个

      作为默认的内容,在父组件没有使用slot时,会渲染这段默认的文本;如果写入了slot,那就会替换整个。所以上例渲染后的结果为:

      分发的内容

      更多分发的内容

      注意,子组件内的备用内容,它的作用域是子组件本身。
      具名slot。给元素指定一个name后可以分发多个内容,具名slot可以与单个slot共存,例如:

      标题

      正文内容

      更多的正文内容

      底部信息

      子组件内声明了3个元素,其中在

      内的没有使用name特性,它将作为默认slot出现,父组件没有使用slot特性的元素与内容都将出现在这里。如果没有指定默认的匿名slot,父组件内多余的内容片段都将被抛弃。上例最终渲染结果为:

      标题

      正文内容

      更多的正文内容

      在组合使用组件时,内容分发API至关重要
      作用域插槽。是一种特殊的slot。使用一个可以复用的模板替换已渲染元素。概念比较难理解,简单示例:

      观察子组件的模板,在元素上有一个类似props传递数据给组件的写法 msg = "xxx",将数据传到了插槽。父组件中使用了