組件使用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 }」,是沒法正常工做的,由於在解析一個像這樣的複雜表達式的時候,有不少邊緣狀況須要考慮。

相關文章
相關標籤/搜索