Vue 組件間的通訊

什麼是Vue組件?

組件(Component)是 Vue.js 最強大的功能之一。html

組件能夠擴展 HTML 元素,封裝可重用的代碼。vue

組件系統讓咱們能夠用獨立可複用的小組件來構建大型應用,幾乎任意類型的應用的界面均可以抽象爲一個組件樹:web

組件關係 

Vue 組件通訊.png

上面展現的圖片能夠引入全部 Vue 組件的關係形式:
  • A 組件和 B 組件、B 組件和 C 組件、B 組件和 D 組件造成了父子關係
  • C 組件和 D 組件造成了兄弟關係
  • A 組件和 C 組件、A 組件和 D 組件造成了隔代關係(其中的層級多是多級,即隔多代)

組件通訊

一、props 和 $emit

使用props,父組件可使用props向子組件傳遞數據。vuex

父組件

<template>
    <son :message="son"></son>
</template>

<script>

import son from './son.vue';

export default {
    components: {
        son
    },
    data () {
        return {
            message: 'father message';
        }
    }
}
</script>

子組件

<template>
    <div>{{message}}</div>
</template>

<script>
export default { 
  /**
   * 獲得父組件傳遞過來的數據
   * 這裏的定義最好是寫成數據校驗的形式,省得獲得的數據是咱們意料以外的
   *
   * props: {
   *   message: {
   *     type: String,
   *     default: ''
   *   }
   * }
   *
  */
  props:['message'], 
}
</script>

父組件向子組件傳遞事件方法,子組件經過$emit觸發事件,回調給父組件。數組

父組件

<template>
    <son @getChildData="getChildData"></son>
</template>

<script>

import son from './son.vue';

export default {
    components: {
        son
    },
    methods: {
        // 執行子組件觸發的事件
        getChildData(val) {
          console.log(val);
        }
    }
}
</script>    

子組件

<template>
    <div>
      <input type="text" v-model="myMessage" @input="passData(myMessage)">
    </div>
</template>

<script>
export default {
    props: {
        msg: {
            type: String,
            required: true
        }
    },
    data() {
      return {
        // 這裏是必要的,由於你不能直接修改 props 的值
        myMessage: this.message
      }
    },
    methods () {
        passData(val) {
           // 數據狀態變化時觸發父組件中的事件
           this.$emit('getChildData', val);
        }
    }
}
</script>

在上面的例子中,有父組件 father 和子組件 son。緩存

  • 父組件傳遞了 message 數據給子組件,而且經過v-on綁定了一個 getChildData 事件來監聽子組件的觸發事件;
  • 子組件經過 props 獲得相關的 message 數據,而後將數據緩存在 data 裏面,最後當屬性數據值發生變化時,經過 this.$emit 觸發了父組件註冊的 getChildData 事件處理數據邏輯。

 二、$attrs 和 $listeners

第一種方式處理父子組件之間的數據傳輸有一個問題:若是父組件A下面有子組件B,組件B下面有組件C,這時若是組件A想傳遞數據給組件C怎麼辦呢?
若是採用第一種方法,咱們必須讓組件A經過prop傳遞消息給組件B,組件B在經過prop傳遞消息給組件C;要是組件A和組件C之間有更多的組件,那採用這種方式就很複雜了。Vue 2.4開始提供了$attrs和$listeners來解決這個問題,可以讓組件A之間傳遞消息給組件C。dom

  • $attrs:包含了父做用域中不被 prop 所識別 (且獲取) 的特性綁定 (class 和 style 除外)。當一個組件沒有聲明任何 prop 時,這裏會包含全部父做用域的綁定 (class 和 style 除外),而且能夠經過 v-bind="$attrs" 傳入內部組件。一般配合 inheritAttrs 選項一塊兒使用。ide

  • $listeners:包含了父做用域中的 (不含 .native 修飾器的) v-on 事件監聽器。它能夠經過 v-on="$listeners" 傳入內部組件ui

父組件

<template>
    <div>
        <p>我是父組件</p>
        <son :son="son" :grandSon="grandSon" @getSonData="getSonData" @getGrandSonData="getGrandSonData"></son>
    </div>
</template>

<script>
import son from './son.vue';

export default {
    components: {
        son
    },
    data() {
        return {
            son: '爺爺的兒子',
            grandSon: '爺爺的孫子'
        }
    },
    methods: {
        // 來自子組件觸發的事件
        getSonData(val) {
            console.log(val)
        },
        // 來自孫組件觸發的事件
        getGrandSonData(val) {
            console.log(val)
        }
    }
}
</script>

子組件

<template>
    <div>
        <p>我是子組件</p>
        <input type="text" v-model="myMessage" @input="passData(myMessage)">
        <grandson :child="child" v-bind="$attrs" v-on="$listeners"></grandson>
    </div>
</template>

<script>
import son from './son.vue';

export default {
    components: {
        grandson
    },
    props: ['son'],
    data() {
        return {
            child: '爸爸的兒子',
            myMessage: ''
        }
    },
    // 默認爲true,若是傳入的屬性子組件沒有prop接受,就會以字符串的形式出現爲標籤屬性
    // 設爲false,在dom中就看不到這些屬性,試一下就知道了
    inheritAttrs: false,
    created () {
        // 在子組件中打印的$attrs就是父組件傳入的值,刨去style,class,和子組件中已props的屬性
        console.log(this.$attrs) // 打印爺爺的孫子
    },
    methods:{
        passData(val) {
            // 觸發父組件中的事件
            this.$emit('getSonData', val)
        }
    }
}
</script>

孫組件

<template>
    <div>
        <p>我是孫組件</p>
        <input type="text" v-model="myMessage" @input="passData(myMessage)">
        {{$attrs.grandSon}}
        {{$attrs.child}}
    </div>
</template>

<script>
export default {
    data() {
        return {
            myMessage: ''
        }
    },
    inheritAttrs: false,
    created () {
        // 打印爺爺的孫子和爸爸的兒子
        console.log(this.$attrs)
    },
    methods:{
        passData(val) {
            // 觸發父組件中的事件
            this.$emit('getGrandSonData', val)
        }
    }
}
</script>

在上面的例子中,咱們定義了 父,子,孫 三個組件,其中組件子是組件父的子組件,組件孫是組件子的子組件。this

  • 孫組件中能直接觸發getGrandSonData的緣由在於子組件調用孫組件時 使用 v-on 綁定了$listeners 屬性
  • 經過v-bind 綁定$attrs屬性,孫組件能夠直接獲取到父組件中傳遞下來的props(除了子組件中props聲明的)

三、v-model

父組件經過v-model傳遞值給子組件時,會自動傳遞一個value的prop屬性,在子組件中經過this.$emit(‘input', val)自動修改v-model綁定的值

父組件

<template>
    <div>
        <p>我是父組件</p>
        {{message}}
        <son v-model="message"></son>
    </div>
</template>

<script>
import son from './son.vue';

export default {
    components: {
        son
    },
    data() {
        return {
            message: '個人兒子'
        }
    }
}
</script>    

子組件

<template>
    <div>
        <p>我是子組件</p>
        <input type="text" v-model="myMessage" @input="passData(myMessage)">
    </div>
</template>

<script>
export default {
    //v-model會自動傳遞一個字段爲value的prop屬性 
    props: ['value'],
    data() {
        return {
            myMessage: this.message
        }
    },
    methods:{
        passData(val) {
            //經過如此調用能夠改變父組件上v-model綁定的值 
            this.$emit('input', val)
        }
    }
}
</script>
    

在上面的實例代碼中,咱們定義了 father 和 son 兩個組件,這兩個組件是父子關係,v-model 也只能實現父子組件之間的通訊。

  • 在 father 組件中,咱們給自定義的 son 組件實現了 v-model 綁定了 message 屬性。此時至關於給 son 組件傳遞了 value 屬性和綁定了 input 事件。
  • 在定義的 son 組件中,能夠經過 props 獲取 value 屬性,根據 props 單向數據流的原則,又將 value 緩存在了 data 裏面的 myMessage 上,再在 input 上經過 v-model 綁定了 myMessage 屬性和一個 input 事件。當 input 值變化時,就會觸發 input 事件,處理 father 組件經過 v-model 給 son 組件綁定的 input 事件,觸發 father 組件中 message 屬性值的變化,完成 child 子組件改變 father 組件的屬性值。

四、provide和inject

父組件中經過provider來提供變量,而後在子組件中經過inject來注入變量。不論子組件有多深,只要調用了inject那麼就能夠注入provider中的數據。而不是侷限於只能從當前父組件的prop屬性來獲取數據,只要在父組件的生命週期內,子組件均可以調用。

父組件

<template>
    <div>
        <p>我是父組件</p>
    </div>
</template>

<script>
export default {
    provide:{
        message: '中午一塊兒吃飯'
    }
}
</script>

子組件

<template>
    <div>
        <p>我是子組件</p>
        {{message}} //會顯示中午一塊兒吃飯
    </div>
</template>

<script>
export default {
    inject:['message'],//獲得父組件傳遞過來的數據
}
</script>

在上面的實例中,咱們定義了組件 father 和組件 son,組件 father 和組件 son 是父子關係。

  • 在 father 組件中,經過 provide 屬性,以對象的形式向子孫組件暴露了一些屬性
  • 在 son 組件中,經過 inject 屬性注入了 father 組件提供的數據,實際這些經過 inject 注入的屬性是掛載到 Vue 實例上的,因此在組件內部能夠經過 this 來訪問。

五、$parent 和 $children

這裏要說的這種方式就比較直觀了,直接操做父子組件的實例。$parent 就是父組件的實例對象,而 $children 就是當前實例的直接子組件實例了,不過這個屬性值是數組類型的,且並不保證順序,也不是響應式的。

父組件

<template>
    <div>
        <p>我是父組件</p>
        <button @click="changeChildValue">test</button>
        <son></son>
        <grandson></grandson>
    </div>
</template>

<script>
import son from './son.vue'
import grandson from './grandSon.vue'
export default {
  components: {
    son,
    grandSon
  },
  data() {
    return {
        fatherMessage: ''
    }
  }
  methods: {
    changeChildValue(){
      this.$children[0].sonMessage = 'hello';
    }
  }
}
</script>

子組件

<template>
    <div>
        <p>我是子組件</p>
        <input type="text" v-model="mymessage" @change="changeValue" /> 
    </div>
</template>

<script>
export default {
  data() {
    return {
      mymessage: this.$parent.fatherMessage
    }
  },
  methods: {
    changeValue(){
      this.$parent.fatherMessage = this.mymessage;//經過如此調用能夠改變父組件的值
    }
  }
}
</script>
在上面實例代碼中,分別定義了 father 和 son 組件,這兩個組件是直接的父子關係。兩個組件分別在內部定義了本身的屬性。在 father 組件中,直接經過 this.$children[0].sonMessage = 'hello';son 組件內的 sonM essage 屬性賦值,而在 son 子組件中,一樣也是直接經過 this.$parent.fatherMessagefather 組件中的 fatherMessage 賦值,造成了父子組件通訊。

六、 中央事件總線

對於父子組件之間的通訊,上面的兩種方式是徹底能夠實現的,可是對於兩個組件不是父子關係,那麼又該如何實現通訊呢?在項目規模不大的狀況下,徹底可使用中央事件總線 EventBus 的方式。 

在組件以外定義一個bus.js做爲組件間通訊的橋樑,適用於比較小型不須要vuex又須要兄弟組件通訊的

1. bus.js中添加以下
 import Vue from 'vue'
 export const Bus = new Vue();
2.組件中調用bus.js經過自定義事件傳遞數據
import Bus from './bus.js' 
  export default { 
      methods: {
         bus () {
            Bus.$emit('msg', '我要傳給兄弟組件們')
         }
      }
  }
3.兄弟組件中監聽事件接受數據
import Bus from './bus.js'
export default {
    mounted() {
      Bus.$on('msg', (e) => {
         console.log(e)
     })
    }
}

中央事件總線 EventBus 很是簡單,就是任意組件和組件之間打交道,沒有多餘的業務邏輯,只須要在狀態變化組件觸發一個事件,而後在處理邏輯組件監聽該事件就能夠。

相關文章
相關標籤/搜索