Vue.js 父子組件通訊的1212種方式

面試官:Vue 中父子組件通訊有哪些方式?html

本身先想一分鐘。前端

無能否認,如今不管大廠仍是小廠都已經用上了 Vue.js 框架,簡單易上手不說,教程詳盡,社區活躍,第三方套件還多。真的是前端開發人員必備技能。並且在面試當中也每每會問到關於 Vue 方面的各類問題,其中大部分面試官會問到如上這種問題。vue

最近一直在作 Vue項目代碼層面上的優化,說實話,優化別人的代碼真是件痛苦的事情,功能實現尚且不說,就說代碼規範我就能再寫出一篇文章來。真的是無規範不成方圓,規範這個東西過重要了!有點扯了,回到主題,咳咳,那就談談我對上面的面試題的理解吧,文筆有限,不妥之處,歡迎在文章結尾留言斧正啊,正啊,啊!git

清單

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

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

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

1. Prop

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

<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
  }
})
複製代碼

你能夠狠狠的戳這裏查看Demo!瀏覽器輸出:api

from parent
複製代碼

2. $emit

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

<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(監聽)了這個事件及其監聽回調。其實至關於下面這種寫法:app

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 監聽器。說白了就是讓咱們手動進行更新父組件中的值了,從而使數據改動來源更加的明顯。下面引入自官方的一段話:

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

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

<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>
複製代碼

廢話這麼多,如何作到「雙向綁定」 呢?讓咱們進段廣告,廣告以後更加精彩! ... 好的,歡迎回來。假如咱們想實現這樣一個效果:改變子組件文本框中的值同時改變父組件中的值。怎麼作?列位不妨先想一想。先看段代碼:

<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 被識別 (且獲取) 的特性綁定 (classstyle 除外)。當一個組件沒有聲明任何 prop 時,這裏會包含全部父做用域的綁定 (classstyle 除外),而且能夠經過 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" 一級級的往下傳遞,子子孫孫無窮盡也!

一個插曲!

當咱們在組件上賦予了一個非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 的描述:

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

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

<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
  }
})
複製代碼

你能夠狠狠的戳這裏查看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

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

  • $root

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

  • broadcast / dispatch

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

總結

囉嗦了這麼多,但願看到的同窗或多或少有點收穫吧。不對的地方還請留言指正,不勝感激。父子組件間的通訊其實有不少種,就看你在哪些狀況下去用。不一樣場景不一樣對待。前提是你要心中有數才行!經過大神之路還有很遠,只要天天看看社區,看看文檔,寫寫Demo,天天進步一點點,總會有收穫的。俗話說,三人行則必有我師,但願更多志同道合的小夥伴能聚在一塊兒交流技術!下面的羣快滿了,能夠加我 Q1769617251 。備註 vue 便可。

相關文章
相關標籤/搜索