打开玩耍Vue组件的正确姿势

2018-07-19更新:

  1. 更新作用域插槽的属性: scope -> slot-scope;
  2. 添加了对象解构。

现在把组件中基本的知识梳理一下。


组件的基本使用

注册组件

注册组件就是利用Vue.component()方法,先传入一个自定义组件的名字,然后传入这个组件的配置。

 Vue.component('mycomponent',{
    template: `
这是一个自定义组件
`, data () { return { message: 'hello world' } } })

如上方式,就已经创建了一个自定义组件,然后就可以在Vue实例挂在的DOM元素中使用它。

 

直接使用Vue.component()创建的组件,所有的Vue实例都可以使用。还可以在某个Vue实例中注册只有自己能使用的组件。

var app = new Vue({
    el: '#app',
    data: {
    },
    components: {
      'my-component': {
        template: `
这是一个局部的自定义组件,只能在当前Vue实例中使用
`, } } })

模板的要求

注意:组件的模板只能有一个根元素。下面的情况是不允许的。

template: `
这是一个局部的自定义组件,只能在当前Vue实例中使用
`,

组件中的data必须是函数

可以看出,注册组件时传入的配置和创建Vue实例差不多,但也有不同,其中一个就是data属性必须是一个函数。
这是因为如果像Vue实例那样,传入一个对象,由于JS中对象类型的变量实际上保存的是对象的引用,所以当存在多个这样的组件时,会共享数据,导致一个组件中数据的改变会引起其他组件数据的改变。

而使用一个返回对象的函数,每次使用组件都会创建一个新的对象,这样就不会出现共享数据的问题来了。

关于DOM模板的解析

当使用 DOM 作为模版时 (例如,将 el 选项挂载到一个已存在的元素上), 你会受到 HTML 的一些限制,因为 Vue 只有在浏览器解析和标准化 HTML 后才能获取模板内容。尤其像这些元素 

      ...

      自定义组件  被认为是无效的内容,因此在渲染的时候会导致错误。这时应使用特殊的 is 属性:

      也就是说,标准HTML中,一些元素中只能放置特定的子元素,另一些元素只能存在于特定的父元素中。比如table中不能放置divtr的父元素不能div等。所以,当使用自定义标签时,标签名还是那些标签的名字,但是可以在标签的is属性中填写自定义组件的名字。

      应当注意,如果您使用来自以下来源之一的字符串模板,这些限制将不适用:

      • 当父组件传递值为基本类型时,在子组件中更改这个属性会报错。正确的做法是,在父组件中绑定属性值时,加上.sync修饰符。

        然后在子组件中改变相应的属性

            methods: {
             changeArray () {
               this.counter++
               this.$emit('update:childArray', this.counter)
             }
           }

      子组件希望对传入的prop进行操作

      一般来说,是不建议在子组件中对父组件中传递来的属性进行操作的。如果真的有这种需求,可以这样:

      1. 父组件传递了一个基本类型值,那么可以在子组件中创建一个新的属性,并以传递进来的值进行初始化,之后就可以操作这个新的属性了

        props: ['initialCounter'],
        data: function () {
          return { counter: this.initialCounter }
        }
      2. 父组件传递了一个引用类型值,为了避免更改父组件中相应的数据,最好是对引用类型进行复制。复杂的情况,肯定应该是深复制。

      给子组件传递正确类型的值

      同样是上面的原因,静态的给子组件的特性传递值,它都会把他当做一个字符串。

      
      

      子组件中,特性的值是字符串 "1" 而不是 number 1。如果想传递正确的数值,应该使用v-bind传递,这样就能把传递的值当做一个表达式来处理,而不是字符串。

      
      

      Prop验证

      我们可以给组件的props属性添加验证,当传入的数据不符合要求时,Vue会发出警告。

      Vue.component('example', {
        props: {
          // 基础类型检测 (`null` 意思是任何类型都可以)
          propA: Number,
          // 多种类型
          propB: [String, Number],
          // 必传且是字符串
          propC: {
            type: String,
            required: true
          },
          // 数字,有默认值
          propD: {
            type: Number,
            default: 100
          },
          // 数组/对象的默认值应当由一个工厂函数返回
          propE: {
            type: Object,
            default: function () {
              return { message: 'hello' }
            }
          },
          // 自定义验证函数
          propF: {
            validator: function (value) {
              return value > 10
            }
          }
        }
      })

      type 可以是下面原生构造器:

      • String
      • Number
      • Boolean
      • Function
      • Object
      • Array
      • Symbol

      type 也可以是一个自定义构造器函数,使用 instanceof 检测。

       // 自定义Person构造器
       function Person(name, age) {
          this.name = name
          this.age = age
        }
        Vue.component('my-component', {
          template: `
      名字: {{ person-prop.name }}, 年龄: {{ person-prop.age }}
      `, props: { person-prop: { type: Person // 指定类型 } } }) new Vue({ el: '#app2', data: { person: 2 // 传入Number类型会报错 } })

      非Prop类型的属性

      也可以像在html标签中添加data-开头的自定义属性一样,给自定义组件添加任意的属性。而不仅限于data-*形式,这样做的话,Vue会把这个属性放在自定义组件的根元素上。一个自定义组件的模板只能有一个根元素

      覆盖非Prop属性

      如果父组件向子组件的非prop属性传递了值,那么这个值会覆盖子组件模板中的特性。

      上面渲染的结果是,divatt属性是helloParent
      注意:前面已经提到过,覆盖原则对于classstyle不适用,而是采用了合并(merge)的原则。

      上面的渲染结果是,div的类名是class1 class2,行内样式是color:red; background:yellow;

      自定义事件

      通过prop属性,父组件可以向子组件传递数据,而子组件的自定义事件就是用来将内部的数据报告给父组件的。

      如上所示,共分为以下步骤:

      1. 子组件在自己的方法中将自定义事件以及需要发出的数据通过以下代码发送出去

        this.$emit('myclick', '这是我暴露出去的数据', '这是我暴露出去的数据2')
        • 第一个参数是自定义事件的名字
        • 后面的参数是依次想要发送出去的数据
      2. 父组件利用v-on为事件绑定处理器

        这样,在Vue实例的methods方法中就可以调用传进来的参数了

      注意: 在使用v-on绑定事件处理方法时,不应该传进任何参数,而是直接写v-on:myclick="onClick",不然,子组件暴露出来的数据就无法获取到了

      绑定原生事件

      如果想在某个组件的根元素上监听一个原生事件。可以使用 .native 修饰 v-on

      探究v-model

      v-model可以对表单控件实现数据的双向绑定,它的原理就是利用了绑定属性和事件来实现的。比如input控件。不使用v-model,可以这样实现数据的双向绑定:

      {{text}}

      上面的代码同样实现了数据的双向绑定。其本质就是:

      • inputvalue特性绑定到Vue实例的属性text上,text改变,input中的内容也会改变
      • 然后把表单的input事件处理函数设置为Vue实例的一个方法,这个方法会根据输入参数改变Vue中text`的值
      • 相应的,在input中输入内容时,触发了input事件,把event.target.value传给这个方法,最后就实现了改变绑定的数据的效果。

      v-model就是上面这种方式的语法糖,也就是把上面的写法封装了一下,方便我们使用。

      使用自定义事件创建自定义的表单输入组件

      理解了v-model的内幕,也就可以把这个效果用在自定义表单组件上了。
      来实现一个简单的只能输入hello的表单输入组件。

      {{hello}}

      定制组件的v-model

      默认情况下,一个组件的 v-model 会使用 value 属性和 input 事件,但是诸如单选框、复选框之类的输入类型可能把 value 属性用作了别的目的。model 选项可以回避这样的冲突:

      Vue.component('my-checkbox', {
        model: {
          prop: 'checked',   // 将输入的特性改为checked
          event: 'change'        // 触发的自定义事件类型为change
        },
        props: {
          checked: Boolean,
          // this allows using the `value` prop for a different purpose
          value: String
        }
      })

      这样设置的话,

      上面的代码就等同于

       { foo = val }" value="some value">

      实际使用时,与之前不同的地方是:

      1. 把子组件中接收外部数据的prop属性改为checked
      2. 向父组件发出事件时,事件类型应改为change
      Vue.component('my-component3', {
          template: ``,
          props: ['checked'],        // 属性名改变
          model: {
            prop: 'checked',
            event: 'change'
          },
          methods: {
            checkInput (value) {
              var hello = 'hello'
              if (!hello.includes(value)) {
                this.$emit('change', hello)   // 事件类型改变
                this.$refs.input.value = hello
              } else {
                this.$emit('change', value)  // 事件类型改变
              }
            }
          }
        })

      动态组件

      通过使用保留的  元素,动态地绑定到它的 is 特性,可以让多个组件使用同一个挂载点,并动态切换:

       

      也可以直接绑定到组件对象上:

      var Home = {
        template: `
      这是home组件
      ` } new Vue({ el: '#app6', data: { currentComponent: Home } })

      保留切换出去的组件,避免重新渲染

      如果把切换出去的组件保留在内存中,可以保留它的状态或避免重新渲染。为此可以添加一个 keep-alive 指令参数:

      
        
          
        
      

      使用slot分发内容

      终于到了基本知识的最后一个了。官网写的很详细。

      单个slot

      上面用到的很多组件的使用方式是这样的:

      也就是说组件中是空的,没有放置任何文本或元素。但是原生的html元素都是可以进行嵌套的,div里面放table
      什么的。自定义组件开闭标签之间也可以放置内容,不过需要在定义组件时使用slot

      slot相当于子组件设置了一个地方,如果在调用它的时候,往它的开闭标签之间放了东西,那么它就把这些东西放到slot中。

      1. 当子组件中没有slot时,父组件放在子组件标签内的东西将被丢弃;
      2. 子组件的slot标签内可以放置内容,当父组件没有放置内容在子组件标签内时,slot中的内容会渲染出来;
      3. 当父组件在子组件标签内放置了内容时,slot中的内容被丢弃

      子组件的模板:

      我是子组件的标题

      只有在没有要分发的内容时才会显示。

      父组件模板:

      我是父组件的标题

      这是一些初始内容

      渲染结果:

      我是父组件的标题

      我是子组件的标题

      这是一些初始内容

      具名slot

      slot可以有很多个。那么子组件对于父组件放置的多余的内容如何放到各个slot中呢?方法就是子组件给每个slot起一个名字name,父组件放置多余的元素时,给每个元素的slot属性分配一个代表slot的名字。到时候,多余的内容就会根据自己的slot属性去找具有对应名字的slot元素。

      注意

      1. 子组件可以有一个匿名的slot,当分发的多余内容找不到对应的slot时,就会进入这里面
      2. 如果子组件没有匿名的slot,当分发的多余内容找不到对应的slot时,就会被丢弃

      例如,假定我们有一个 app-layout 组件,它的模板为:

      父组件模版:

      
        

      这里可能是一个页面标题

      主要内容的一个段落。

      另一个主要段落。

      这里有一些联系信息

      渲染结果为:

      这里可能是一个页面标题

      主要内容的一个段落。

      另一个主要段落。

      这里有一些联系信息

      作用域插槽

      作用域插槽也是一个插槽slot,但是他可以把数据传递给到父组件的特定元素内,然后有父组件决定如何渲染这些数据。

      1. 首先,子组件的slot需要有一些特性(prop)

          Vue.component('my-component4', {
             template: `
        `, data () { return { hello: [1,'2'] } } })
      2. 父组件在调用子组件时,需要在里面添加一个template元素,并且这个template元素具有scope特性

        scope特性的值,就代表了所有子组件传过来的数据组成的对象。相当于

        props = {
            text: '',
           message: ''
        }
      3. 最后,父组件就可以在template中渲染子组件传过来的数据了

          
      4. 2018-07-19更新:
        最新的Vue支持将作用域插槽的属性解构。所以上述代码可以简写为:

          

      作用域插槽也是插槽,只不过是多加了些特性,然后父组件多进行了些处理。

               打开玩耍Vue组件的正确姿势_第1张图片

       

你可能感兴趣的:(vue,组件,技术,软件开发)