Vue組件通訊大全(終結篇)

背景

  Vue是單頁面應用,單頁面應用又是由組件構成,各個組件之間又互相關聯,那麼如何實現組件之間通訊就顯得尤其重要了。就像人是由各類器官組成,那麼組件之間的通訊就像是血液同樣將養分(數據)輸送到各個部位,爲了保證數據流向的簡潔性,使程序更易於理解,因此vue提倡單項數據流。組件之間的通訊主要分爲三種,父子組件通訊、孫子組件通訊和非關聯組件通訊。html

父子組件通訊

props和$emit

  這種方式是你們最常常用到的,父組件經過v-bind綁定數據,子組件經過props接收父組件傳過來的數據,利用$emit觸發指定事件,父組件經過$on監聽子組件觸發的對應事件,這裏就不舉例了。主要講一下prop:vue

  • Vue數據是單向數據流,這樣設計的目的是爲了保證數據流向的簡潔性,使程序更易於理解,每次父級組件發生更新時,子組件中全部prop都將會刷新爲最新的值,prop會在子組件建立以前傳遞,因此能夠在data和computed中直接使用。
  • 不該該在一個子組件內部改變prop,這樣會破壞單向的數據綁定,致使數據流難以理解。若是有這樣的須要,能夠經過 data 屬性接收或使用 computed 屬性進行轉換。
  • 若是prop傳遞的是基本類型,這時候改變prop的數據就會報錯,若是是引用數據類型若是改變原始數據不會報錯,可是從新賦值或改變某個屬性值就會報錯,利用這一點就可以實現父子組件數據的「雙向綁定」,雖然這樣實現可以節省代碼,但會犧牲數據流向的簡潔性,使人難以理解,最好不要這樣去作。想要實現父子組件的數據「雙向綁定」,可使用 v-model 或 .sync,下面會講到。

v-model

v-model實現父子組件數據的雙向綁定,它的本質是v-bind和v-on的語法糖,在一個組件上使用v-model,默認會爲組件綁定名爲value的屬性和名爲input的事件。例如:vuex

<test-model
v-bind:value="haorooms"
v-on:input="haorooms=$event"></test-model>
<script>
export default {
   data () {
     haorooms: ''
   }
}
</script>
複製代碼

等價於瀏覽器

<test-model v-model="haorooms"></test-model>
<script>
export default {
   data () {
     haorooms: ''
   }
}
</script>
複製代碼

子組件bash

<template>
<div>
  <input  
   v-bind:value="value"
   v-on="$emit('input', $event.target.value)">
</div>
</template>
<script>
  export default {
    props: ['value'],
    model: {
       prop: 'value',
       event: 'input'
    }
  }
</script>
複製代碼

.sync

.sync修飾符它的本質和v-model相似,它的本質也是v-bind和v-on的語法糖,例如:app

<test-model
v-bind:title="doc.title"
v-on:update:title="doc.title=$event"></test-model>
<script>
export default {
   data () {
     doc: {
        title: ''
     }
   }
}
</script>
複製代碼

等價於ide

<test-model v-bind:title.sync="doc.title"></test-model>
<script>
export default {
   data () {
      doc: {
        title: ''
     }
   }
}
</script>
複製代碼

子組件post

<template>
<div>
  <input v-model="value">
</div>
</template>
<script>
  export default {
     data () {
        value: ''
     },
    watch: {
       value (val) {
          this.$emit('update:title', val)
       }
    }
  }
</script>
複製代碼

這種是綁定一個值的狀況,還能夠綁定多個值:優化

<template>
 <div id="demo">
  <test-model v-bind.sync="haorooms"></test-model>
</div>
</template>
<script>
  import testModel from './testModel'
  export default {
   data(){
     return{
       haorooms: {
         name: 'aaa',
         age: 18,
         value: 10
       }
     }
  },
  components: {
     testModel,
  },
  watch: {
     haorooms: {
       handler (val) {
         console.log('test', val)
       },
       deep: true
      }
  }
}
</script>
複製代碼

子組件ui

<template>
    <div></div>
</template>
<script>
    export default {
        data () {
            return {
                test: ''
            }
        },
        mounted () {
            this.$emit('update:name', 111)
        }
    }
</script>
複製代碼

咱們看到這種方式是將haorooms對象中name、age、value三個屬性都實現了雙向綁定,在子組件中觸發事件的時候須要指定某個屬性來觸發。

注意帶有 .sync 修飾符的 v-bind 不能和表達式一塊兒使用 (例如 v-bind:title.sync=」doc.title + ‘!’」 是無效的)。取而代之的是,你只能提供你想要綁定的屬性名,相似 v-model。 將 v-bind.sync 用在一個字面量的對象上,例如 v-bind.sync=」{ title: doc.title }」,是沒法正常工做的,由於在解析一個像這樣的複雜表達式的時候,有不少邊緣狀況須要考慮。

v-model和.sync對比

共同點:

  • v-model和.sync均可以實現父子組件數據的雙向綁定,本質都是v-bind和v-on的語法糖。 不一樣點:
  • v-model默認會爲組件綁定名爲value的屬性和名爲input的事件,而.sync能夠自定義傳入的屬性和事件。
  • v-model只能實現某一個屬性的雙向綁定,.sync能夠實現多個屬性的雙向綁定。
  • 寫法不一樣v-model須要在子組件中寫porp來接收數據,而.sync是利用$attrs來接收數據,因此不須要寫prop。

$parent、$children和ref

  這三種方式都是經過直接獲得組件實例,能夠實現父子組件、兄弟組件、跨級組件等數據通訊,不過通常不建議使用,由於會增長組件間的耦合,並且要判斷組件存不存在,若是不存在可能會遇到報錯,這幾種方式比較簡單也不在這裏舉例了。

跨級組件

$attrs和$listeners

$attrs
背景

  隨着項目複雜度的提升,組件嵌套的層級愈來愈深,以前的組件通訊通常使用v-bind和prop組合使用,可是咱們發現這種方式只是適用於父子組件,若是是孫子組件的話就須要將父組件的數據傳遞給子組件,子組件的數據再傳遞給孫組件,這樣子就須要寫不少prop,有沒有哪一種方式能夠直接將父組件直接傳遞給孫組件,讓代碼更加簡潔?這就是$attrs的由來,解決跨組件數據傳遞,注意只對孫子組件有效,可是class和style數據傳遞除外。

使用

首先咱們有三個嵌套組件父A-子B-孫C,而後咱們想讓A中的數據傳入C中,用prop的作法是這樣子的:

<div id="app">
    A{{msg}}
    <component-b :msg="msg"></component-b>
  </div>
複製代碼
<script>
    let vm = new Vue({
      el: '#app',
      data: {
        msg: '100'
      },
      components: {
        'ComponentB': {
          props: ['msg'],
          template: `<div>B<component-c :msg="msg"></component-c></div>`,
          components: {
            'ComponentC': {
              props: ['msg'],
              template: '<div>C{{msg}}</div>'
            }
          }
        },
        
      }
    })
  </script>
複製代碼

組件B並無使用父組件傳遞過來的msg,而是直接傳遞給組件C,除了這樣子有沒有什麼方式直接將數據傳遞給C呢,下面咱們來看$attrs的寫法:

<script>
    let vm = new Vue({
      el: '#app',
      data: {
        msg: '100'
      },
      components: {
        'ComponentB': {
          template: `<div>B<component-c v-bind="$attrs"></component-c></div>`,
          components: {
            'ComponentC': {
              props: ['msg'],
              template: '<div>C{{msg}}</div>'
            }
          }
        },
        
      }
    })
  </script>
複製代碼

總結:爲了解決跨組件通訊,而提出了$attrs。prop和$attrs均可以用來父子組件通訊,接收父組件傳遞過來的數據,可是prop的優先級高於$attrs,若是子組件中prop、$attrs都有寫,那麼數據只會被prop接收,注意$attrs不能接收class和style傳過來的數據。

inheritAttrs

背景
<template>
  <div class="home">
    <mytest  :title="title" :message="message"></mytest>
  </div>
</template>
<script>
export default {
  name: 'home',
  data () {
    return {
      title:'title1111',
      message:'message111'
    }
  },
  components:{
    'mytest':{
      template:`<div>這是個h1標題{{title}}</div>`,
      props:['title'],
      data(){
        return{
          meg:'111'
        }
      },
      created:function(){
        console.log(this.$attrs)//注意這裏
      }
    }
  }
}
</script>
複製代碼

  上面的代碼,咱們在組件裏只是用了title這個屬性,message屬性沒有用到,那麼下瀏覽器渲染出來是什麼樣呢?以下圖:

image.png

咱們看到:組件內未被註冊的屬性將做爲普通html元素屬性在子組件的根元素上渲染,雖然在通常狀況下不會對子組件形成影響,可是就怕遇到一些特殊狀況,好比:

<template>
    <childcom :name="name" :age="age" type="text"></childcom>
</template>
<script>
export default {
    'name':'test',
    props:[],
    data(){
        return {
            'name':'張三',
            'age':'30',
            'sex':'男'
        }
    },
    components:{
        'childcom':{
            props:['name','age'],
            template:`<input type="number" style="border:1px solid blue">`,
        }
    }
}
</script>
複製代碼

image.png

咱們看到父組件的type="text"覆蓋了input上type="number",這不是我想要的,我須要input上type=number類型不變,可是我仍是想取到父組件上的type="text"的值,這時候inheritAttrs就派上用場了。

<template>
    <childcom :name="name" :age="age" type="text"></childcom>
</template>
<script>
export default {
    'name':'test',
    props:[],
    data(){
        return {
            'name':'張三',
            'age':'30',
            'sex':'男'
        }
    },
    components:{
        'childcom':{
            inheritAttrs:false,
            props:['name','age'],
            template:`<input type="number" style="border:1px solid blue">`,
            created () {
                console.log(this.$attrs.type)
            }
        }
    }
}
</script>
複製代碼

image.png

總結:默認狀況下父組件傳遞數據給子組件可是沒被prop特性綁定將會回退且做爲普通的html特性應用在子組件的根元素上。inheritAttrs屬性用來去掉這種默認行爲,來避免不可預知的影響。注意 inheritAttrs: false 選項不會影響 style 和 class 的綁定。

$listeners

背景

上面講了$attrs是爲了跨組件傳遞數據,那若是想經過孫子組件來給父組件傳遞數據呢?以前的作法也是一層一層的向上傳遞,好比用$emit方法,可是子組件若是用不到,只是想改變父組件的數據,這時候咱們就可使用$listeners。

<template>
    <div>
        <childcom :name="name" :age="age" :sex="sex" @testChangeName="changeName"></childcom>
    </div>
</template>
<script>
export default {
    'name':'test',
    props:[],
    data(){
        return {
            'name':'張三',
            'age':'30',
            'sex':'男'
        }
    },
    components:{
        'childcom':{
            props:['name'],
            template:`<div>
                <div>我是子組件   {{name}}</div>
                <grandcom v-bind="$attrs" v-on="$listeners"></grandcom>
            </div>`,
           
            components: {
                'grandcom':{
                    template:`<div>我是孫子組件-------<button @click="grandChangeName">改變名字</button></div>`,
                    methods:{
                        grandChangeName(){
                           this.$emit('testChangeName','kkkkkk')
                        }
                    }
                }
            }
        }
    },
    methods:{
        changeName(val){
            this.name = val
        }
    }
}
</script>
複製代碼

$listeners是一個對象,裏面包含了做用在這個組件上的全部監聽器。

參考文章:www.jianshu.com/p/ce8ca875c…

provide和inject

背景

  項目越複雜,組件嵌套的層級就越深,那麼子組件怎樣實現跟祖先組件的通訊問題,這就是provide和inject提出的緣由。

簡介

  這個方法容許一個祖先組件向其全部子孫後代注入一個依賴,不論組件嵌套的層次有多深,並在起上下游關係成立的時間裏始終有效。一言以蔽之:祖先組件中經過provider來提供變量,而後在子孫組件中經過inject來注入變量。例如: 假設有兩個組件:A和B,A是B組件的祖先組件

// A.vue
export default {
  provide: {
    name: '天涯'
  }
}
複製代碼
// B.vue
export default {
  inject: ['name'],
  mounted () {
    console.log(this.name);  // 天涯
  }
}
複製代碼

咱們能夠看到在祖先組件A中提供了一個變量,那麼在其全部的後代組件中均可以注入這個變量並使用。

因此,上面 A.vue 的 name 若是改變了,B.vue 的 this.name 是不會改變的,仍然是天涯。

實際上,你能夠把依賴注入看做一部分「大範圍有效的 prop」,除了:

  • 祖先組件不須要知道哪些後代組件使用它提供的屬性
  • 後代組件不須要知道被注入的屬性來自哪裏

然而,依賴注入仍是有負面影響的。它將你應用程序中的組件與它們當前的組織方式耦合起來,使重構變得更加困難。同時所提供的屬性是非響應式的。這是出於設計的考慮,由於使用它們來建立一箇中心化規模化的數據跟使用$root作這件事都是不夠好的。若是你想要共享的這個屬性是你的應用特有的,而不是通用化的,或者若是你想在祖先組件中更新所提供的數據,那麼這意味着你可能須要換用一個像Vuex這樣真正的狀態管理方案了。

非關聯組件通訊

vuex

  vuex想必你們都很是熟悉了,它是vue的狀態管理管理中心,存儲的數據是響應式的,但並不會保存起來,刷新以後就回到了初始狀態,具體作法應該在vuex數據發生改變的時候把數據拷貝一份保存到localStorage裏面,這樣子也能夠實現實現父子組件、兄弟組件、跨級組件、非關聯組件等數據通訊。在這裏也再也不舉例。

eventBus

  它的實現思想也很好理解,在要互相通訊的兩個組件中,都引入同一個新的vue實例,而後在兩個組件中經過分別調用這個實例的事件觸發和監聽來實現通訊。

//eventBus.js
import Vue from 'vue';  
export default new Vue();
複製代碼
<!--組件A-->
<script>
import Bus from 'eventBus.js'; 
export default {
    methods: {  
        sayHello() {  
            Bus.$emit('sayHello', 'hello');   
        }  
    } 
}
</script>
複製代碼
<!--組件B-->
<script>
import Bus from 'eventBus.js'; 
export default {
    created() {  
        Bus.$on('sayHello', target => {  
            console.log(target);  // => 'hello'
        });  
    } 
}
</script>
複製代碼

$root

  經過$root根組件任何組件均可以獲取當前組件樹的根vue實例,經過維護根實例上的data,就能夠實現組件間的數據共享。

// 組件A
<script>
export default {
    created() {  
        this.$root.$emit('changeTitle', '我是A')
    }
}
</script>
複製代碼
// 組件B
<script>
export default {
    created() {  
        this.$root.$on('changeTitle')
    },
    methods: {
      changeTitle (title)  {
         console.log(title)
      }
   }
}
</script>
複製代碼

這種方式有個弊端就是組件A和組件B必須同時存在。下面用另一種方式能夠優化:

//main.js 根實例
new Vue({
    el: '#app',
    store,
    router,
    // 根實例的 data 屬性,維護通用的數據
    data: function () {
        return {
            author: ''
        }
    }, 
    components: { App },
    template: '<App/>',
});
複製代碼
<!--組件A-->
<script>
export default {
    created() {  
        this.$root.author = '因而乎'
    }
}
</script>
複製代碼
<!--組件B-->
<template>
    <div><span>本文做者</span>{{ $root.author }}</div>
</template>
複製代碼

  經過這種方式,雖然能夠實現通訊,但在應用的任何部分,任什麼時候間發生的任何數據變化,都不會留下變動的記錄,這對於稍複雜的應用來講,調試是致命的,不建議在實際應用中使用。

總結: 本文講了組件之間的各類通訊: 父子組件通訊:prop和$emit、v-model、.sync、$parent、children和ref

父子組件雙向綁定:props和$emit、v-model、.sync

跨級組件通訊:$attrs和$listeners、provide和inject

非關聯組件通訊:vuex、eventBus、$root

參考文章:juejin.im/post/5d0489…

juejin.im/post/5cde0b…

相關文章
相關標籤/搜索