组件使用v-model、$listeners、.sync(区别于v-model的双向数据绑定)

自定义组件

  1. 自定义组件的v-modelhtml

    首先咱们先说一下在自定义组件中使用v-model的必要条件vue

    • 在自定义的组件中要有input(这里咱们先不讨论单选复选框)
    • 在自定义组件的模板对象中要有props属性,且里面要含有一个value
    • 在自定义组件的input标签上要绑定value属性值为props中传入的值,且还须要发出一个input事件

    这样讲可能会有点难理解,仍是上代码吧...vuex

    <div id="app">
      <child-com v-model="message"></child-com>
      <span>{{ message }}</span>
    </div>
    <template id="childCom">
      <div>
        <input type="text" :value="value" @input='inputEvent'>
      </div>
    </template>
    
    <script>
      const childCom = {
        template: '#childCom',
        props: ['value'],
        methods: {
          inputEvent(event) {
            this.$emit('aaa', event.target.value)
          }
        },
      }
    
      const vm = new Vue({
        el: '#app',
        data: {
          message: '能够双向绑定的了'
        },
        components: {
          childCom
        }
      })
    </script>

    这是最终实现效果须要必备的,看完这些代码若是你是小白,你可能会有点不理解为何要这样作,下面我告诉你原理。app

    首先在咱们使用的v-model 中,其内部实现的原理就是一个 value属性和一个input事件,其主要步骤就是,用v-bind绑定value,而后用input事件监听值的变化,当文本框中的值发生变化的时候,input事件就会触发,那么咱们能够在input事件中获取到改变后的值而后赋值给value,这样是否是就完成了双向数据绑定了。上代码:this

    <div id="app">
      <input type='text' :value='message' @input='inputEvent'>
      <span>{{ message }}</span>
    </div>
    
    <script>
      const vm = new Vue({
        el: '#app',
        data: {
          message: '能够双向绑定的了'
        },
        methods: {
          inputEvent(event) {
            this.message = event.target.value;
          }
        }
      })
    </script>

    就这样几个步骤,就达到了v-model的效果了,这就是他的原理,而后让咱们深一步想,让自定义组件使用双向数据绑定。由于咱们知道其内部就是value和input事件,spa

    因此有了以下代码:双向绑定

    <div id="app">
        <child-com :value='message' @input='message=$event'></child-com> <!-- 此代码就这里和最开始代码不一样 -->
        <span>{{ message }}</span>
      </div>
    
      <template id="childCom">
        <div>
          <input type="text" :value="value" @input='inputEvent'>
        </div>
      </template>
    
      <script>
        const childCom = {
          template: '#childCom',
          props: ['value'],
          methods: {
            inputEvent(event) {
              this.$emit('input', event.target.value)
            }
          },
        }
    
        const vm = new Vue({
          el: '#app',
          data: {
            message: '能够双向绑定的了'
          },
          components: {
            childCom
          }
        })
    </script>

    根据上面的原理,如今你应该知道了为何要传一个value在子组件了吧,明白以后,您就能够把 <child-com :value='message' @input='message=$event'></child-com> 替换成 <child-com v-model="message"></child-com> 了。code

  2. $listeners的使用component

    由来:当咱们在项目开发过程当中会出现不少组件嵌套的关系,那么若是还要在最外层的组件向内部传递数据的话,有以下几种方式:htm

    • 从父向子传递,子再向孙传递,一直传递下去,那么最里面的组件想往最外层传东西则能够从最里面向外面逐层$emit发送出去,可是仔细想一想,一个简单的传递信息,却涉及到了这之间全部的组件,而他们只是一个中间者,这让代码维护起来很是困难
    • 使用vuex来进行传递,这样确实方便了不少,可是这样作若是没有其余用处的话就有点大材小用了
    • 使用事件总线,这样使用也不容易维护

    $listeners$attrs 的出现,就完美的解决了第一种状况的发生

    <div id="app">
        <child-com :name='name' :age='age' @test-listeners='testListeners'></child-com>
      </div>
    
      <script>
        const vm = new Vue({
          el: '#app', //  父组件
          data: {
            name: 'lyl',
            age: 20,
          },
          methods: {
            testListeners(arg) {
              console.log(arg)
            }
          },
          components: {
            childCom: { //  子组件
              inheritAttrs: false,
              template: `
                <div>
                  <span> {{name}} </span>
                  <grand-com v-bind='$attrs' v-on='$listeners'></grand-com>
                </div>
              `,
              props: ['name'],
              components: {
                grandCom: { //  孙子组件
                  inheritAttrs: false,
                  template: `
                    <div>
                      <span @click='listenClick'>{{$attrs.age}}</span>
                    </div>
                  `,
                  methods: {
                    listenClick() {
                      this.$emit('test-listeners','aaaaaaa');
                    }
                  },
                }
              }
            }
          }
        })
    </script>

    上面代码中,孙子组件要发出一个是将让父组件调用,这个时候咱们能够在中间过渡的子组件模板使用的孙子组件上绑定这个属性,即:v-on='$listeners',这样一来父组件就能直接调用孙子组件发出的方法了,而且在中间层的子组件上并无什么多余的部分

  3. .sync的使用方法

    咱们都知道,在一个组件上,咱们只能使用一个v-model,可是若是咱们的组件中有多个input标签呢,而且每一个input标签中的值都不一样,且每一个都想进行双向绑定,这个时候,v-model就不行了。因而乎就出现了.sync的出现。

    根据上面我说的那些需求,咱们写一下代码:

    • 不使用.sync的代码
    <div id="app">
        <child-com 
          :value='obj.value' @aaa='obj.value = $event'
          :name='obj.name'  @bbb='obj.name = $event'
          :age='obj.age' @ccc='obj.age = $event'>
        </child-com>
    
        <p>{{ obj }}</p>
        <p>{{ obj.value }}</p>
        <p>{{ obj.name }}</p>
        <p>{{ obj.age }}</p>
      </div>
    
    <!-- childCom组件的模板 -->
      <template id="childCom">
        <div>
          <input type="text" :value="value" @input='inputValueEvent'>
          <br>
          <input type="text" :value="name" @input='inputNameEvent'>
          <br>
          <input type="text" :value="age" @input='inputAgeEvent'>
        </div>
      </template>
    
      <script>
        const childCom = {
          template: '#childCom',
          props: ['value','name','age'],
          methods: {
            inputValueEvent(event) {
              this.$emit('aaa', event.target.value)
            },
            inputNameEvent(event) {
              this.$emit('bbb', event.target.value)
            },
            inputAgeEvent(event) {
              this.$emit('ccc', event.target.value)
            }
          },
        }
    
        const vm = new Vue({
          el: '#app',
          data: {
            obj: { value: '双向绑定' , name: 'coderlyl' , age: 20}
          },
          components: {
            childCom
          }
        })
    </script>

    根据上面的代码,咱们不难发现,咱们在 标签中书写了过多的重复的东西,可读性也不是很好,下面咱们再使用 .sync 的方式

    • 使用.sync的代码
    <div id="app">
        <child-com v-bind:value.sync='obj.value' 
                   v-bind:name.sync="obj.name" 
                   v-bind:age.sync="obj.age">    <!--这里也发生了变化-->
        </child-com>
    
        <p>{{ obj }}</p>
        <p>{{ obj.value }}</p>
        <p>{{ obj.name }}</p>
        <p>{{ obj.age }}</p>
      </div>
      <template id="childCom">
        <div>
          <input type="text" :value="value" @input='inputValueEvent'>
          <br>
          <input type="text" :value="name" @input='inputNameEvent'>
          <br>
          <input type="text" :value="age" @input='inputAgeEvent'>
        </div>
      </template>
    
      <script>
        const childCom = {
          template: '#childCom',
          props: ['value','name','age'],
          methods: {
            inputValueEvent(event) {
              this.$emit('update:value', event.target.value) // 这里发生了变化
            },
            inputNameEvent(event) {
              this.$emit('update:name', event.target.value) // 这里发生了变化
            },
            inputAgeEvent(event) {
              this.$emit('update:age', event.target.value) // 这里发生了变化
            }
          },
        }
    
        const vm = new Vue({
          el: '#app',
          data: {
            message: '能够双向绑定的了',
            obj: { value: '双向绑定' , name: 'coderlyl' , age: 20}
          },
          components: {
            childCom
          }
        })
    </script>

    也许看完这里,你并无以为好到哪里去了,下面还有更简单的写法

    <child-com v-bind.sync="obj"></child-com>
    <!-- 其余代码同样 -->

    对,没错!这是终极简化版,可是这只针对于对象才能用

    注意带有 .sync 修饰符的 v-bind 不能和表达式一块儿使用 (例如 v-bind:title.sync=”doc.title + ‘!’” 是无效的)。取而代之的是,你只能提供你想要绑定的属性名,相似 v-model

    v-bind.sync 用在一个字面量的对象上,例如 v-bind.sync=”{ title: doc.title }”,是没法正常工做的,由于在解析一个像这样的复杂表达式的时候,有不少边缘状况须要考虑。

相关文章
相关标签/搜索