Vue 組件通信

組件作為Vue中的核心概念,是值得我們深入研究的課題之一,通過研究它,我們可以理解更高深的思想,可以提升自己的開發(fā)技巧。而今天,我要討論的是Vue的組件通信。
眾所周知,組件通信是通過props和emit去完成的,但實際上,這只是眾多方式中的一種而已。而且針對不同的情況,會有更合適的方法。下面就聽我慢慢道來。

1.props和emit

父組件:
<template>
<div class="parent-box">
      <h3>我是父元素,props方式</h3>
      <p class="content">
        通過$emit獲得子元素屬性{{children2}}
      </p>
      <Children2 @changeChild2="changeChild2"></Children2>
</div>
</template>

<script>
data(){
    return {
      children2:'children2',
    }
  },
  methods:{
    changeChild2(val){
      this.children2 = val
    },
}
</script>

子組件
<template>
    <div class="children-box">
        <h4>我是子元素</h4>
        <button @click="clickEvent">改值</button>
        <p>通過props通信 {{value}}</p>
    </div>
</template>
<script>
    export default {
        props:['value'],
        methods:{
            clickEvent(){
                // 核心代碼
                this.$emit('changeChild2',Math.random())
            }
        }
    }
</script>

大體效果如下:


20190407_211309.gif

可以看到,通過點擊按鈕,可以改變通過props傳入子組件的value屬性。因為這種方式是大家最常用的一種方式,這里就不做詳細解釋了。

2.$parent$children

$parent 屬性可以用來從一個子組件訪問父組件的實例。它提供了一種機會,可以在后期隨時觸達父級組件,以替代將數據以 prop 的方式傳入子組件的方式。
$children可以訪問當前實例的直接子組件。
下面來看一個例子,代碼如下,注意注釋部分。

父組件
<template>
<div class="parent-box">
      <h3>我是父元素,$parent,$children方式</h3>
      <p class="content">
        通過$children獲得子元素屬性{{children1}}
      </p>
      <Children1></Children1>
</div>
</template>
<script>
data(){
    return {
      parent1:'parent1',
      children1:'children1',
    }
  },
 mounted() {
    // 核心代碼,通過$children獲取子組件的屬性
    this.children1 = this.$children[0]._data.value
  },
</script>
子組件
<template>
    <div class="children-box">
        <h4>我是子元素</h4>
        <input type="text" v-model="parent">
        <p>通過$parent獲取父元素的屬性 {{parent}}</p>
    </div>
</template>
<script>
    export default {
        data(){
            return {
                // 核心代碼,通過$parent獲得父組件的屬性
                parent:this.$parent._data.parent1
            }
        },
        mounted() {
        },
        watch:{
            parent(val){
                // 核心代碼,改變父組件中的屬性
                this.$parent._data.parent1 = val
            }
        }
    }
</script>

大體效果如下:


20190407_212944.gif

可以看到,通過this.$parent._data.parent1 = val,改變子組件中parent的值,然后賦值給父組件的parent1可以直接改變父組件的屬性值。
雖然這種方式比較方便快捷,但有很大的副作用,就如官網所說:

在絕大多數情況下,觸達父級組件會使得你的應用更難調試和理解,尤其是當你變更了父級組件的數據的時候。當我們稍后回看那個組件的時候,很難找出那個變更是從哪里發(fā)起的。

節(jié)制地使用 $parent$children - 它們的主要目的是作為訪問組件的應急方法。更推薦用 props 和 events 實現父子組件通信

3.總線方式

有時候,我們的組件并不止父子關系這么簡單,可能兄弟組件之間也要進行通信,而EventBus就能解決這個問題,相對于vuex它更輕量,不需要我們引入vuex這個龐然大物,更加適合小型項目。
我們在主實例App之外,單獨定義一個空的Bus實例,來進行組件間的通信。
下面來看一個例子,代碼如下

bus.js
// 核心代碼
import Vue from 'vue'
var Bus = new Vue()
export default Bus

父組件:
<template>
<div class="parent-box">
      <h3>我是父元素,總線方式</h3>
      <Children31></Children31>
      <Children32></Children32>
</div>
</template>

子組件1
<template>
    <div class="children-box">
        <h4>我是子元素</h4>
        <p>通過總線方式通信 {{msg}}</p>
    </div>
</template>
<script>
    import Bus from '../bus'
    export default {
        data(){
            return {
                msg:'hello world'
            }
        },
        mounted() {
        },
        created(){
              // 核心代碼,接受事件
            Bus.$on('setMsg',val=>{
                this.msg = val
            })
        },
    }
</script>
子組件2
<template>
    <div class="children-box">
        <h4>我是子元素</h4>
        <input type="text" v-model="msg">
        <p>通過總線方式通信 {{msg}}</p>

    </div>
</template>
<script>
    import Bus from '../bus'
    export default {
        data(){
            return {
                msg:'hello world'
            }
        },
        mounted() {
        },
        watch:{
            msg:function (newVal) {
                // 核心代碼發(fā)出事件
                Bus.$emit('setMsg',newVal)
            },
        }
    }
</script>

大體效果如下:


20190407_215821.gif

可以看到,我改變子組件2 input的值會觸發(fā)emit事件,去改變子組件1中的msg。

4.$attrs$listeners

$attrs$listeners是2.4.0才新加入的方法,用來解決組件的跨級傳輸非常有用。試想有A、B、C三個組件,A包含B,B包含C,如果我想在A上給C傳參,并且接收C的事件怎么辦呢?
原先,我們只使用pros去傳參的話,就只能拿B作為中轉組件,B組件定義足夠多的pros,不僅僅用于自身,還要用于傳輸給C,而事件的傳遞,也只能一層層地往上傳,這樣就會使代碼很繁瑣,臃腫,不利于維護。
$attrs$listeners就是用來處理這種情況的,代碼如下

父組件A
<template>
<div class="parent-box">
      <h3>我是父元素A,$attrs,$listeners方式</h3>
      <Children4 :value1="value1" :value2="value2" @clickEvent1="clickEvent1" @clickEvent2="clickEvent2"></Children4>
</div>
</template>
<script>
data(){
      value1:'B',
      value2:'C',
},
methods:{
clickEvent1(){
      this.value1 = Math.random()
    },
    clickEvent2(){
      this.value2 = Math.random()
    }
}
</script>
子組件B
<template>
    <div class="children-box">
        <h4>我是子元素B</h4>
        <p>{{value1}}</p>
        <button @click="clickEvent">改變value1的值</button>
        <Children42 v-bind="$attrs" v-on="$listeners"></Children42>
    </div>
</template>
<script>
    import Children42 from './Children4.2'
    export default {
        name:'Children41',
        inheritAttrs:false,
        props:['value1'],
        components:{
            Children42
        },
        methods:{
            clickEvent(){
                this.$emit('clickEvent1')
            }
        },
    }
</script>
子組件C
<template>
    <div class="children-box">
        <h4>我是子元素C</h4>
        <p>{{value2}}</p>
        <button @click="clickEvent">改變value1的值</button>
    </div>
</template>
<script>
    export default {
        name:'Children42',
        inheritAttrs:false,
        props:['value2'],
        data(){
            return {
            }
        },
        mounted() {
        },
        methods:{
            clickEvent(){
                this.$emit('clickEvent2')
            }
        },
    }
</script>

大體效果如下:


20190407_221354.gif

可以看到,我們只要在引用C組件的時候,加入v-bind="$attrs" v-on="$listeners"兩個屬性即可,這樣,C組件就可以接收到來自A組件的值,A組件也能接收到來自C組件的事件。
如此以來,就不需要在B組件定義中轉的屬性和方法,如果你的組件結構比較復雜,這種方式可以很大程度減少代碼的冗余,更加的輕量化。

5.provide和inject

這對選項需要一起使用,以允許一個祖先組件向其所有子孫后代注入一個依賴,不論組件層次有多深,并在起上下游關系成立的時間里始終生效。如果你熟悉 React,這與 React 的上下文特性很相似。
這相對于attrs和listeners可能更加簡介,只需要父組件提供變量,子組件注入就行,不需要在中間組件寫什么代碼,但并不推薦在業(yè)務代碼中使用,正如官方所說。

provide 和 inject 主要為高階插件/組件庫提供用例。并不推薦直接用于應用程序代碼中。

因為provide inject 會有一個類似冒泡的特性,數據源有可能在中間被”“打斷”,甚至是有可能被組件庫中的組件打斷,或者打斷組件庫中的provide,不利于維護

代碼如下:

父組件
<template>
<div class="parent-box">
      <h3>我是父元素,provide,inject方式</h3>
      <Children5></Children5>
    </div>
</template>
<script>
data(){
return {
     theme:'blue'
},
// 核心代碼
provide(){
    return {
      test:this
    }
},
}
</script>

子組件
<template>
    <div class="children-box">
        <h4 :style="{color:value.theme}">我是子元素</h4>
        <div @click="changeValue">改顏色</div>
    </div>
</template>
<script>
    export default {
        // 核心代碼
        inject: {
            value:{
                from:'test',
                default:()=>{}
            }
        },
        methods:{
            changeValue(){
                this.value.theme = 'red'
            }
        }
    }
</script>

大體效果如下:


20190407_223227.gif

我們在父組件中提供一個test屬性,然后賦值為this,這里之所以賦值this,是為了讓provide和inject的綁定變成可響應的,這樣,我再子組件中就可以直接改變父組件的theme屬性。

6. Vuex

Vuex 是一個專為 Vue.js 應用程序開發(fā)的狀態(tài)管理模式。它采用集中式存儲管理應用的所有組件的狀態(tài),并以相應的規(guī)則保證狀態(tài)以一種可預測的方式發(fā)生變化。
Vuex的功能強大,但應對簡單的組件通信用Vuex就顯得多余了,有種殺雞用牛刀的感覺,還會增加我們代碼的理解難度。
Vuex作為我們必須掌握的技能之一,這里也不再贅述,不了解的話,官網就是最好的學習材料。

總結

vue中組件通信的方式很多,應對不同情況,靈活地采用最適合的方式,才能使我們的代碼變得優(yōu)雅。

以上是目前為止,我所知的所有通信方式,如有遺漏,歡迎補充。

以下,是代碼的demo地址
https://github.com/hanwolfxue/blog-demo-vue-communicate.git

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,563評論 6 544
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 99,694評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,672評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,965評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,690評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 56,019評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,013評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,188評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發(fā)現了一具尸體,經...
    沈念sama閱讀 49,718評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,438評論 3 360
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,667評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,149評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,845評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,252評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,590評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,384評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,635評論 2 380

推薦閱讀更多精彩內容