vue 組件的通訊方式(完整版)

幾種通訊方式無外乎如下幾種:html

  • Prop(經常使用)
  • $emit (組件封裝用的較多)
  • .sync語法糖 (較少)
  • $attrs & $listeners (組件封裝用的較多)
  • provide & inject (高階組件/組件庫用的較多)
  • slot-scope & v-slot (vue@2.6.0+)新增
  • scopedSlots 屬性
  • 其餘方式通訊

下面逐個介紹,大神請繞行。vue

1. Prop

英式發音:[prɒp]。這個在咱們平常開發當中用到的很是多。簡單來講,咱們能夠經過 Prop 向子組件傳遞數據。用一個形象的比喻來講,父子組件之間的數據傳遞至關於自上而下的下水管子,只能從上往下流,不能逆流。這也正是 Vue 的設計理念之單向數據流。而 Prop 正是管道與管道之間的一個銜接口,這樣水(數據)才能往下流。說這麼多,看代碼:git

<div id="app">
  <child :content="message"></child>
</div>
// Js
let Child = Vue.extend({
  template: '<h2>{{ content }}</h2>',
  props: {
    content: {
      type: String,
      default: () => { return 'from child' }
    }
  }
})

new Vue({
  el: '#app',
  data: {
    message: 'from parent'
  },
  components: {
    Child
  }
})

2. $emit

英式發音:[iˈmɪt]。官方說法是觸發當前實例上的事件。附加參數都會傳給監聽器回調。按照個人理解不知道能不能給你們說明白,先簡單看下代碼吧:github

<div id="app">
  <my-button @greet="sayHi"></my-button>
</div>
let MyButton = Vue.extend({
  template: '<button @click="triggerClick">click</button>',
  data () {
    return {
      greeting: 'vue.js!'
    }
  },
  methods: {
    triggerClick () {
      this.$emit('greet', this.greeting)
    }
  }
})

new Vue({
  el: '#app',
  components: {
    MyButton
  },
  methods: {
    sayHi (val) {
      alert('Hi, ' + val) // 'Hi, vue.js!'
    }
  }
})

你能夠狠狠的戳這裏查看Demo! 大體邏輯是醬嬸兒的:當我在頁面上點擊按鈕時,觸發了組件 MyButton 上的監聽事件 greet,而且把參數傳給了回調函數 sayHi 。說白了,當咱們從子組件 Emit(派發) 一個事件以前,其內部都提早在事件隊列中 On(監聽)了這個事件及其監聽回調。其實至關於下面這種寫法:vuex

vm.$on('greet', function sayHi (val) {
  console.log('Hi, ' + val)
})
vm.$emit('greet', 'vue.js')
// => "Hi, vue.js"

3. .sync 修飾符

這個傢伙在 vue@1.x 的時候曾做爲雙向綁定功能存在,即子組件能夠修改父組件中的值。由於它違反了單向數據流的設計理念,因此在 vue@2.0 的時候被幹掉了。可是在 vue@2.3.0+ 以上版本又從新引入了這個 .sync 修飾符。可是此次它只是做爲一個編譯時的語法糖存在。它會被擴展爲一個自動更新父組件屬性的 v-on 監聽器。說白了就是讓咱們手動進行更新父組件中的值了,從而使數據改動來源更加的明顯。下面引入自官方的一段話:api

在有些狀況下,咱們可能須要對一個 prop 進行「雙向綁定」。不幸的是,真正的雙向綁定會帶來維護上的問題,由於子組件能夠修改父組件,且在父組件和子組件都沒有明顯的改動來源。app

既然做爲一個語法糖,確定是某種寫法的簡寫形式,哪一種寫法呢,看代碼:ide

<text-document
  v-bind:title="doc.title"
  v-on:update:title="doc.title = $event">
</text-document>

因而咱們能夠用 .sync 語法糖簡寫成以下形式:函數

<text-document v-bind:title.sync="doc.title"></text-document>

假如咱們想實現這樣一個效果:改變子組件文本框中的值同時改變父組件中的值。怎麼作?post

<div id="app">
  <login :name.sync="userName"></login> {{ userName }}
</div>
let Login = Vue.extend({
  template: `
    <div class="input-group">
      <label>姓名:</label>
      <input v-model="text">
    </div>
  `,
  props: ['name'],
  data () {
    return {
      text: ''
    }
  },
  watch: {
    text (newVal) {
      this.$emit('update:name', newVal)
    }
  }
})

new Vue({
  el: '#app',
  data: {
    userName: ''
  },
  components: {
    Login
  }
})

你能夠狠狠的戳這裏查看Demo!下面劃重點,代碼裏有這一句話:

this.$emit('update:name', newVal)

官方語法是:update:myPropName 其中 myPropName 表示要更新的 prop 值。固然若是你不用 .sync 語法糖使用上面的 .$emit 也能達到一樣的效果。僅此而已!

4. $attrs 和 $listeners

  • 官網對 $attrs 的解釋以下:

包含了父做用域中不做爲 prop 被識別 (且獲取) 的特性綁定 (class 和 style 除外)。當一個組件沒有聲明任何 prop 時,這裏會包含全部父做用域的綁定 (class 和 style 除外),而且能夠經過 v-bind="$attrs" 傳入內部組件——在建立高級別的組件時很是有用。

  • 官網對 $listeners 的解釋以下:

包含了父做用域中的 (不含 .native 修飾器的) v-on 事件監聽器。它能夠經過 v-on="$listeners" 傳入內部組件——在建立更高層次的組件時很是有用。

我以爲 $attrs 和 $listeners 屬性像兩個收納箱,一個負責收納屬性,一個負責收納事件,都是以對象的形式來保存數據。看下面的代碼解釋:

<div id="app">
  <child 
    :foo="foo" 
    :bar="bar"
    @one.native="triggerOne"
    @two="triggerTwo">
  </child>
</div>

從 Html 中能夠看到,這裏有倆屬性和倆方法,區別是屬性一個是 prop 聲明,事件一個是 .native 修飾器。

let Child = Vue.extend({
  template: '<h2>{{ foo }}</h2>',
  props: ['foo'],
  created () {
    console.log(this.$attrs, this.$listeners)
    // -> {bar: "parent bar"}
    // -> {two: fn}
    
    // 這裏咱們訪問父組件中的 `triggerTwo` 方法
    this.$listeners.two()
    // -> 'two'
  }
})

new Vue({
  el: '#app',
  data: {
    foo: 'parent foo',
    bar: 'parent bar'
  },
  components: {
    Child
  },
  methods: {
    triggerOne () {
      alert('one')
    },
    triggerTwo () {
      alert('two')
    }
  }
})

你能夠狠狠的戳這裏查看Demo! 能夠看到,咱們能夠經過 $attrs 和 $listeners 進行數據傳遞,在須要的地方進行調用和處理,仍是很方便的。固然,咱們還能夠經過 v-on="$listeners" 一級級的往下傳遞,子子孫孫無窮盡也!同時,子孫若是用了 $listeners 的方法,也能夠經過 $emit 向該方法的源頭傳參,這個源頭的方法就能夠接收到該子孫傳來的參數。

一個插曲!

當咱們在組件上賦予了一個非Prop 聲明時,編譯以後的代碼會把這些個屬性都當成原始屬性對待,添加到 html 原生標籤上,看上面的代碼編譯以後的樣子:

<h2 bar="parent bar">parent foo</h2>

這樣會很難看,同時也爆了某些東西。如何去掉?這正是 inheritAttrs 屬性的用武之地!給組件加上這個屬性就好了,通常是配合 $attrs 使用。看代碼:

// 源碼
let Child = Vue.extend({
  ...
  inheritAttrs: false, // 默認是 true
  ...
})

再次編譯:

<h2>parent foo</h2>

5. provide / inject

他倆是對CP, 感受挺神祕的。來看下官方對 provide / inject 的描述:

provide 和 inject 主要爲高階插件/組件庫提供用例。並不推薦直接用於應用程序代碼中。而且這對選項須要一塊兒使用,以容許一個祖先組件向其全部子孫後代注入一個依賴,不論組件層次有多深,並在起上下游關係成立的時間裏始終生效。

這對選項須要一塊兒使用,以容許一個祖先組件向其全部子孫後代注入一個依賴,不論組件層次有多深,並在起上下游關係成立的時間裏始終生效。一言而蔽之:祖先組件中經過provider來提供變量,而後在子孫組件中經過inject來注入變量。

provide / inject API 主要解決了跨級組件間的通訊問題,不過它的使用場景,主要是子組件獲取上級組件的狀態,跨級組件間創建了一種主動提供與依賴注入的關係。

看完描述有點懵懵懂懂!一句話總結就是:小時候你老爸什麼東西都先幫你存着等你長大該娶媳婦兒了你要房子給你買要車給你買只要他有的儘可能都會知足你。

下面是這句話的代碼解釋:

<div id="app">
  <son></son>
</div>
let Son = Vue.extend({
  template: '<h2>son</h2>',
  inject: {
    house: {
      default: '沒房'
    },
    car: {
      default: '沒車'
    },
    money: {
      // 長大工做了雖然有點錢
      // 僅供生活費,須要向父母要
      default: '¥4500'
    }
  },
  created () {
    console.log(this.house, this.car, this.money)
    // -> '房子', '車子', '¥10000'
  }
})

new Vue({
  el: '#app',
  provide: {
    house: '房子',
    car: '車子',
    money: '¥10000'
  },
  components: {
    Son
  }
})

須要注意的是:provide 和 inject 綁定並非可響應的(即不會當一個變化時另外一個也跟着變化),這是刻意爲之的。然而,若是你傳入了一個可監聽的對象,那麼其對象的屬性仍是可響應的----vue官方文檔

——解決方法:使用 Vue.observable 優化響應式 provide

你能夠狠狠的戳這裏查看Demo!

6. slot-scope & v-slot

關於這種方式的介紹,請看我這篇文章的介紹。傳送門->

7. scopedSlots 屬性

關於這種方式的介紹,請看我這篇文章的介紹。傳送門->

8. 其餘方式通訊

除了以上五種方式外,其實還有:

  • EventBus
思路就是聲明一個全局Vue實例變量 EventBus , 把全部的通訊數據,事件監聽都存儲到這個變量上。能夠閱讀 使用Event Bus進行Vue組件間通訊瞭解更多。這樣就達到在組件間數據共享了,有點相似於 Vuex。但這種方式只適用於極小的項目,複雜項目仍是推薦 Vuex。下面是實現 EventBus 的簡單代碼:
<div id="app">
  <child></child>
</div>
// 全局變量
let EventBus = new Vue()

// 子組件
let Child = Vue.extend({
  template: '<h2>child</h2>',
  created () {
    console.log(EventBus.message)
    // -> 'hello'
    EventBus.$emit('received', 'from child')
  }
})

new Vue({
  el: '#app',
  components: {
    Child
  },
  created () {
    // 變量保存
    EventBus.message = 'hello'
    // 事件監聽
    EventBus.$on('received', function (val) {
      console.log('received: '+ val)
      // -> 'received: from child'
    })
  }
})

你能夠狠狠的戳這裏查看Demo!

  • Vuex

官方推薦的,Vuex 是一個專爲 Vue.js 應用程序開發的狀態管理模式。

  • $parent / $children / $ref

父實例 / 子實例,若是當前實例有的話。經過訪問父實例 / 子實例也能進行數據之間的交互,但極小狀況下會直接修改父 / 子組件中的數據。

不過,這兩種方法的弊端是,沒法在跨級或兄弟間通訊。

  • $root

當前組件樹的根 Vue 實例。若是當前實例沒有父實例,此實例將會是其本身。經過訪問根組件也能進行數據之間的交互,但極小狀況下會直接修改父組件中的數據。

  • broadcast / dispatch

他倆是 vue@1.0 中的方法,分別是事件廣播 和 事件派發。雖然 vue@2.0 裏面刪掉了,但能夠模擬這兩個方法。能夠借鑑 Element 實現。有時候仍是很是有用的,好比咱們在開發樹形組件的時候等等。

總結

常見使用場景能夠分爲三類:

  • 父子通訊:
    父向子傳遞數據是經過 props,子向父是經過 events($emit);經過父鏈 / 子鏈也能夠通訊($parent / $children);ref 也能夠訪問組件實例;provide / inject API;$attrs/$listeners
  • 兄弟通訊:
    Bus;Vuex
  • 跨級通訊:
    Bus;Vuex;provide / inject API、$attrs/$listeners

連接:
相關文章
相關標籤/搜索