Vue組件間通信方式到底有幾種


1. 前言

Vue的一個核心思想就是組件化。所謂組件化,就是把頁面拆分成多個組件 (component),每個組件依賴的 CSSJavaScript、模板、圖片等資源放在一起開發和維護。組件是資源獨立的,組件在系統內部可復用,組件和組件之間可以嵌套。

我們在用 Vue 開發實際項目的時候,就是像搭積木一樣,編寫一堆組件拼裝生成頁面。那么組件之間必然少不了相互通信,而Vue也提供了組件間通信的多種方式,本篇文章就來盤點一下在Vue中組件間通信方式到底有幾種?

2. props / $emit(常用)

props$emit適用於父子組件間通信,父組件通過props的方式向子組件傳遞數據,而子組件可以通過$emit向父組件通信。

2.1 父組件用props向子組件傳遞數據

下面通過一個例子說明父組件如何使用props向子組件傳遞數據:

//父組件
<template>
  <div>
    <h1>父組件</h1>
    <Son :brandList="brandList"></Son>
  </div>
</template>

<script>
import Son from './Son.vue'
export default {
  name: 'Parent',
  components: { Son },
  data() {
    return {
      brandList: ['BMW', 'Benz', 'Audi']
    }
  }
}
</script>
// 子組件
<template>
  <div>
    <h1>子組件</h1>  
    <span v-for="(item, index) in brandList" :key="index">{{item}}</span>
  </div>
</template>

<script>
export default {
  name: 'Son',
  props: ['brandList']
}
</script>

示例說明:

父組件在調用子組件Son時,需要把brandList傳遞給子組件,那么就在調用子組件時把需要傳遞的數據添加到子組件的調用標簽上,如<Son :brandList="brandList"></Son>

而在子組件中,用props屬性來接收父組件傳來的數據brandList,接收之后,子組件就可以在自己的模板中使用傳來的數據了。

這樣,就完成了父組件向子組件的數據傳遞。

2.2 子組件用$emit向父組件傳遞數據

下面通過一個例子說明子組件如何使用$emit向父組件傳遞數據:

// 子組件
<template>
  <div>
    <h1>子組件</h1>  
    <span v-for="(item, index) in brandList" :key="index" @click="emitBrand(item)">			{{item}}
    </span>
  </div>
</template>

<script>
export default {
  name: 'Son',
  props: ['brandList'],
  methods: {
    emitBrand(item){
      this.$emit('onEmitBrand',item)
    }
  }  
}
</script>
//父組件
<template>
  <div>
    <h1>父組件</h1>
    <Son :brandList="brandList"  @onEmitBrand="onEmitBrand"></Son>
  </div>
</template>

<script>
import Son from './Son.vue'
export default {
  name: 'Parent',
  components: { Son },
  data() {
    return {
      brandList: ['BMW', 'Benz', 'Audi']
    }
  },
  methods: {
    onEmitBrand(item) {
      console.log(`您選擇的品牌是${item}`)
    }
  }  
}
</script>

示例說明:

在子組件中,綁定了點擊事件emitBrand,在這個點擊事件中使用$emit廣播了一個名字為onEmitBrand的消息,同時為該消息傳遞item參數。

在父組件中,調用子組件的同時,也監聽了onEmitBrand消息,一旦收到onEmitBrand消息,就會執行對應的回調onEmitBrand函數,在該回調函數中可以接收到子組件廣播消息時傳遞的item參數。

捋一下流程就是:子組件觸發點擊事件emitBrand后,此時廣播onEmitBrand消息,同時攜帶數據item,而此時監聽onEmitBrand消息的父組件就會出發回調onEmitBrand函數,同時獲得攜帶的數據item

這樣,就完成了子組件向父組件的數據傳遞。

3. $parent / children

在父子組件中,使用$parent可以在子組件中獲取父組件的實例,而使用$children可以在父組件中獲取所有子組件實例組成的數組。既然能夠拿到組件實例,就表明可以訪問到此組件的所有東西。

下面通過一個例子來說明:

//父組件
<template>
  <div>
    <h1>父組件</h1>
    <div>父組件值:{{msg}}</div>  
    <p>獲取子組件值:{{this.$children[0].message}}</p>  
    <button @click="changSon">點擊改變子組件值</button>
    <hr/>  
    <h1>子組件</h1>  
    <Son></Son>
  </div>
</template>

<script>
import Son from './Son.vue'
export default {
  name: 'Parent',
  components: { Son },
  data() {
    return {
      msg: 'hello world'
    }
  },
  methods: {
    changSon() {
      this.$children[0].message = '父組件改變了子組件的值'
    }
  }  
}
</script>
// 子組件
<template>
  <div>
    <h1>子組件</h1>  
    <div>子組件值:{{message}}</div>  
    <p>獲取父組件值:{{this.$parent.msg}}</p>
    <button @click="changeParent">點擊改變父組件中的值</button>
  </div>
</template>

<script>
export default {
  name: 'Son',
  data () {
    return {
      message: '這是子組件'
    };
  },
  methods: {
    changeParent(){
      this.$parent.msg = '子組件改變了父組件的值'
    }
  }  
}
</script>

示例說明:

在父組件中通過this.$children可以獲取所有子組件實例組成的數組,然后可以通過數組下標的形式取出所需要的子組件實例,然后就可以訪問或修改子組件的內容了。

同樣,在子組件中通過this.$parent可以獲取父組件的實例,然后就可以訪問或修改子組件的內容了。

值得注意的邊界情況是:

當獲取$parent得到的是new Vue()根實例時,如果在根實例上再獲取$parent將得到的是undefined,同樣,在最底層的子組件中獲取$children將得到的是個空數組。

3.1 擴展內容

如果我們想在子組件中與祖父組件,甚至更上層的組件通信時,此時就會出現this.$parent.$parent.$parent....,同樣,當在上層組件中想與孫子組件,甚至更下層組件通信時,也會出現this.$children.$children.$children....,那么為了避免出現這種不優雅的情況,我們為此封裝兩個方法:$dispatch$broadcast

  • $dispatch——向上派發

    Vue.prototype.$dispatch = function $dispatch(eventName, data) {
      let parent = this.$parent;
      while (parent) {
        parent.$emit(eventName, data);
        parent = parent.$parent;
      }
    };
    

    如果子組件想向它的上層父級組件傳遞數據,我們就通過遞歸獲取this.$parent的方式,一層一層向上廣播消息以及攜帶需要傳遞的數據,然后在目的組件上監聽這個消息即可。

  • $broadcast——向下廣播

    Vue.prototype.$broadcast = function $broadcast(eventName, data) {
      const broadcast = function () {
        this.$children.forEach((child) => {
          child.$emit(eventName, data);
          if (child.$children) {
            $broadcast.call(child, eventName, data);
          }
        });
      };
      broadcast.call(this, eventName, data);
    };
    

    同理,如果父組件想向它的下層子級組件傳遞數據,我們就通過獲取this.$children的方式,一層一層向下廣播消息以及攜帶需要傳遞的數據,然后在目的組件上監聽這個消息即可。

4. provide / inject

provideinject 這對選項需要一起使用,以允許一個祖先組件向其所有子孫后代注入一個依賴,不論組件層次有多深。簡單來說就是父組件中通過provide來提供數據, 然后在子組件中通過inject來接收提供的數據,同時不論子組件嵌套的有多深,只要父級組件通過provide提供了數據,那么子組件就能夠通過inject來接收到。

下面通過一個例子來說明:

//父組件
<template>
  <div>
    <h1>父組件</h1>
    <Son></Son>
  </div>
</template>

<script>
import Son from './Son.vue'
export default {
  name: 'Parent',
  components: { Son },
  provide: {
    NLRX: "難涼熱血"
  }
}
</script>
// 子組件
<template>
  <div>
    <h1>子組件</h1>
    {{NLRX}}  
    <Grandson></Grandson>
  </div>
</template>

<script>
import Grandson from './Grandson.vue'
export default {
  name: 'Son',
  components: { Grandson },
  inject: ['NLRX']
}
</script>
// 孫子組件
<template>
  <div>
    <h1>孫子組件</h1>
    {{NLRX}}  
  </div>
</template>

<script>
export default {
  name: 'Grandson',
  inject: ['NLRX']
}
</script>

示例說明:

上面示例中展示了兩級嵌套的三個組件:父組件——>子組件——>孫子組件。在父組件中使用provide提供了NLRX:'難涼熱血'變量后,在子組件和孫子組件中均能夠使用inject接收到。

5. $attrs / listeners

在vue2.4中引入了$attrs$listeners ,同時新增了inheritAttrs選項,默認為true。這兩個api也可以用於父子組件間的通信,其中$attrs向下傳遞屬性,$listeners向下傳遞方法。

5.1 $attrs向下傳遞屬性

$attrs包含了父作用域中不作為 prop 被識別 (且獲取) 的特性綁定 (classstyle 除外)。當一個組件沒有聲明任何 prop 時,這里會包含所有父作用域的綁定 (classstyle 除外),並且可以通過 v-bind="$attrs" 傳入內部組件。

下面通過一個例子來說明:

// 父組件
<template>
  <div>
    <h1>父組件</h1>
    <Son
      name="難涼熱血"
      age="18"
      gender="男"
      height="175"
      motto="叩首問路,碼夢為生!"
    ></Son>
  </div>
</template>

<script>
import Son from './Son.vue'
export default {
  name: 'Parent',
  components: { Son },
}
</script>
// 子組件
<template>
  <div>
    <h1>子組件</h1>
     <!-- 可以通過v-bind="$attrs"將屬性繼續向下傳遞 --> 
    <Grandson v-bind="$attrs"></Grandson>
  </div>
</template>
<script>
import Grandson from './Grandson.vue'
export default {
  name: 'Son',
  components: { Grandson },
  props: {
    name: String // name作為props屬性綁定
  },
  inheritAttrs: false, // 此選項為false,組件根元素上的沒有在props聲明的屬性可以被$attrs獲取到,
                       // 若為true,則沒有在props聲明的屬性將會“回退”且作為普通的 HTML 特性應用在子組件的根元素上
  created() {
    console.log(this.$attrs);// { "age": "18", "gender": "男", "height": "175", "motto": "叩首問路,碼夢為生!" }
  }
}
</script>
// 孫子組件
<template>
  <div>
    <h1>孫子組件</h1> 
  </div>
</template>

<script>
export default {
  name: 'Grandson',
  props: {
    age: String
  },
  created() {
    console.log(this.$attrs); // { "name": "難涼熱血", "gender": "男", "height": "175", "motto":"叩首問路,碼夢為生!" }
  } 
}
</script>

示例說明:

當父組件調用子組件時,為子組件傳遞了除props選項接收的name屬性之外的其他屬性,如ageheight等,而這些多余的屬性在子組件中可以通過this.$attrs獲取到,同時在子組件調用孫子組件時,通過為孫子組件添加v-bind="$attrs",可以將多余的屬性繼續向下傳遞。以達到父組件向子組件通信的目的。

5.2 $listeners向下傳遞方法

$listeners包含了父作用域中的 (不含 .native 修飾器的) v-on 事件監聽器。它可以通過 v-on="$listeners" 傳入內部組件。

下面通過一個例子來說明:

// 父組件
<template>
  <div>
    <h1>父組件</h1>
    <Son @click="()=>{consoloe.log('難涼熱血')}"></Son>
  </div>
</template>

<script>
import Son from './Son.vue'
export default {
  name: 'Parent',
  components: { Son },
}
</script>
// 子組件
<template>
  <div>
    <h1>子組件</h1>
     <!-- 可以通過v-on="$listeners"將方法繼續向下傳遞 --> 
    <Grandson v-on="$listeners"></Grandson>
  </div>
</template>
<script>
import Grandson from './Grandson.vue'
export default {
  name: 'Son',
  components: { Grandson },
  created() {
    this.listeners.click() // 難涼熱血
  }
}
</script>
// 孫子組件
<template>
  <div>
    <h1>孫子組件</h1> 
  </div>
</template>

<script>
export default {
  name: 'Grandson',
  created() {
    this.listeners.click() // 難涼熱血
  } 
}
</script>

示例說明:

$attrs不同的是,$listeners是傳遞方法,將父組件的方法向下傳遞。

6. ref / $refs

ref 被用來給元素或子組件注冊引用信息。引用信息將會注冊在父組件的 $refs 對象上。如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子組件上,引用就指向組件實例。

下面通過一個例子來說明:

// 父組件
<template>
  <div>
    <h1>父組件</h1>
    <Son ref='son'></Son>
  </div>
</template>

<script>
import Son from './Son.vue'
export default {
  name: 'Parent',
  components: { Son },
  methods: {
    a () {
      const son = this.$refs.son;
      console.log(son.name);  // Son
      console.log(son.message);  // 這是子組件  
      son.sayHello();  // hello
    }
  }  
}
</script>
// 子組件
<template>
  <div>
    <h1>子組件</h1>
  </div>
</template>
<script>
export default {
  name: 'Son',
  data () {
    return {
      message: '這是子組件'
    }
  },
  methods: {
    sayHello () {
      console.log('hello')
    }
  }  
}
</script>

示例說明:

父組件在調用子組件時,為子組件添加了ref屬性,那么在父組件中就可以通過this.$refs.son來獲取到子組件的實例,這樣就可以訪問子組件上的東西了。

7. eventBus(常用)

eventBus 又稱為事件總線,在vue中可以用它來作為組件之間通信的橋梁, 所有組件共用相同的事件中心,所有組件都用它來注冊發送事件和接收事件,其實這就是一個典型的發布訂閱模式,由各個組件向eventBus訂閱事件,並由eventBus發布事件,對於小型不復雜的項目可以使用這種方式。

它的使用方式如下:

  1. 創建一個事件總線並將其導出

    // event-bus.js
    
    import Vue from 'vue'
    export const EventBus = new Vue()
    
  2. 發布事件

       // A組件
       <template>
         <div>
           <h1>A組件</h1>
           <button @click="emitEvent">發布事件</button>   
         </div>
       </template>
       
       <script>
       import {EventBus} from './event-bus.js'    
       export default {
         name: 'ComA',
         methods: {
           emitEvent () {
             EventBus.$emit('nlrx', {
               nlrx:'難涼熱血'
             })
           }
         }  
       }
       </script>
    

    創建一個A組件,並且在A組件中發布一個名為nlrx的消息,消息內容是{nlrx:'難涼熱血'}

  3. 訂閱事件

    // B組件
    <template>
      <div>
        <h1>B組件</h1>
        <button @click="receiveEvent">接收事件</button>   
      </div>
    </template>
    
    <script>
    import {EventBus} from './event-bus.js'    
    export default {
      name: 'ComB',
      mounted() {
        EventBus.$on('nlrx', data => {
          console.log(data.nlrx)  // 難涼熱血
        })
      }
    }
    </script>
    

    創建一個B組件,並且在B組件中訂閱監聽nlrx消息,同時回調函數中的data參數即是消息內容{nlrx:'難涼熱血'}

8. vuex(常用)

Vuex 是一個專為 Vue 應用程序開發的狀態管理模式。它采用集中式存儲管理應用的所有組件的狀態,並以相應的規則保證狀態以一種可預測的方式發生變化。
Vuex 解決了多個視圖依賴於同一狀態和來自不同視圖的行為需要變更同一狀態的問題,將開發者的精力聚焦於數據的更新而不是數據在組件之間的傳遞上。

關於Vuex的具體介紹以及使用方式可參見之前的一篇博文:通俗易懂了解Vuex!

9. 總結

以上就是盤點了Vue中組件間通信的七種方式,這些方式根據使用場景大致可分為:

  • 父子組件通信:props / $emit; $children / $parent ; provide / inject ; ref / refs; $attrs / $listeners
  • 兄弟組件通信:eventBus; vuex
  • 跨級組件通信: eventBusVuexprovide / inject

(完)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM