說說在 Vue.js 中如何實現(xiàn)組件間通信(高級篇)

之前說過捏萍,可以使用 props 將數(shù)據(jù)從父組件傳遞給子組件旷余。其實還有其它種的通信方式,下面我們一一娓娓道來薪鹦。

1 自定義事件

通過自定義事件掌敬,我們可以把數(shù)據(jù)從子組件傳輸回父組件。子組件通過 $emit() 來觸發(fā)事件池磁,而父組件通過 $on() 來監(jiān)聽事件奔害,這是典型的觀察者模式。

html:

<div id="app">
    <p>總數(shù):{{total}}</p>
    <deniro-component @increase="setTotal"
                      @reduce="setTotal"
    ></deniro-component>
</div>

js:

Vue.component('deniro-component', {
    template: '\
    <div>\
    <button @click="increase">+1</button>\
    <button @click="reduce">-1</button>\
    </div>',
    data: function () {
        return {
            counter: 0
        }
    },
    methods: {
        increase: function () {
            this.counter++;
            this.$emit('increase', this.counter);
        },
        reduce: function () {
            this.counter--;
            this.$emit('reduce', this.counter);
        }
    }
});

var app = new Vue({
    el: '#app',
    data: {
        total: 0
    },
    methods: {
        setTotal: function (total) {
            this.total = total;
        }
    }
});

效果:


示例中有兩個按鈕地熄,分別實現(xiàn)加 1 與減 1 操作华临。點擊按鈕后,執(zhí)行組件中定義的 increase 或 reduce 方法端考,在方法內(nèi)部雅潭,使用 $emit 把值傳遞回父組件。 $emit 方法的第一個參數(shù)是使用組件時定義的事件名却特,示例中是 @increase@reduce

<deniro-component @increase="setTotal"
                  @reduce="setTotal"
></deniro-component>

這兩個事件又綁定了 setTotal 方法寻馏,該方法修改了 total 值。 $emit 方法的其它參數(shù)是需要回傳給父組件的參數(shù)核偿。

也可以使用 v-on.native 來監(jiān)聽原生事件诚欠,比如這里監(jiān)聽組件的點擊事件:

html:

<div id="app">
    ...
    <deniro-component ...
                      @click.native="click"
    ></deniro-component>
</div>

js:

...
var app = new Vue({
    el: '#app',
    data: {
        total: 0
    },
    methods: {
        ...
        click: function () {
            console.log("原生點擊事件");
        }
    }
});

這樣,點擊按鈕后漾岳,就可以捕獲原生的點擊事件啦O(∩_∩)O~

注意:這里監(jiān)聽的是這個組件根元素的原生點擊事件轰绵。

2 v-model 方式

也可以使用 v-model 方式來直接綁定父組件變量,把數(shù)據(jù)從子組件傳回父組件尼荆。

html:

<div id="app2">
    <p>總數(shù):{{total}}</p>
    <deniro-component2 v-model="total"
    ></deniro-component2>
</div>

js:

Vue.component('deniro-component2', {
    template: '\
    <div>\
    <button @click="click">+1</button>\
    </div>',
    data: function () {
        return {
            counter: 0
        }
    },
    methods: {
        click: function () {
            this.counter++;
            this.$emit('input', this.counter);
        }
    }
});

var app2 = new Vue({
    el: '#app2',
    data: {
        total: 0
    }
});

效果:

我們使用 v-model="total" 直接綁定變量 total左腔。接著在子組件中,在 $emit 方法傳入事件名 input捅儒,這樣 Vue.js 就會自動找到 `v-model 綁定的變量啦O(∩_∩)O~

我們也可以使用自定義事件來實現(xiàn)上述示例——

html:

<div id="app3">
    <p>總數(shù):{{total}}</p>
    <deniro-component3 @input="setTotal"
    ></deniro-component3>
</div>

js:

Vue.component('deniro-component3', {
    template: '\
    <div>\
    <button @click="click">+1</button>\
    </div>',
    data: function () {
        return {
            counter: 0
        }
    },
    methods: {
        click: function () {
            this.counter++;
            this.$emit('input', this.counter);
        }
    }
});

var app3 = new Vue({
    el: '#app3',
    data: {
        total: 0
    },
    methods: {
        setTotal: function (total) {
            this.total = total;
        }
    }
});

效果與上例相同液样。

我們還可以在自定義的表單輸入組件中利用 v-model,實現(xiàn)數(shù)據(jù)雙向綁定:

html:

<div id="app4">
    <p>總數(shù):{{total}}</p>
    <deniro-component4 v-model="total"
    ></deniro-component4>
    <button @click="increase">+1</button>
</div>

js:

Vue.component('deniro-component4', {
    props: ['value'],
    template: '<input :value="value" @input="update">+1</input>',
    data: function () {
        return {
            counter: 0
        }
    },
    methods: {
        update: function (event) {
            this.$emit('input', event.target.value);
        }
    }
});

var app4 = new Vue({
    el: '#app4',
    data: {
        total: 0
    },
    methods: {
        increase: function () {
            this.total++;
        }
    }
});

效果:

這里我們首先利用 v-model巧还,在自定義組件中綁定了 total 變量鞭莽。然后在組件內(nèi)部,定義了 props 為 ['value']麸祷,注意這里必須為 value澎怒,才能接收綁定的 total 變量。接著在組件模板中把接收到的 value 值(即 total 變量值)阶牍,作為 <input> 元素的初始值喷面,并綁定 input 事件星瘾。下一步,在 input 事件中惧辈,通過 this.$emit('input', event.target.value) 把 total 值傳回父組件的 <button @click="increase">+1</button>琳状。最后在 increase 方法中,遞增 total 值盒齿。

這個示例念逞,我們綜合使用了 props 、v-model和自定義事件县昂,實現(xiàn)了數(shù)據(jù)的雙向綁定肮柜。

總的來說陷舅,一個具有雙向綁定的 v-model 組件具有以下特征:

  1. 使用 props 接收父組件的 value倒彰。
  2. 子組件中擁有可以更新 value 的 HTML 元素,當更新 value 時莱睁,觸發(fā) input 事件待讳。事件內(nèi)部使用 $emit 將新的 value 值回傳給父組件。

3 非父子組件

非父子組件指的是兄弟組件或者跨多級組件仰剿。

3.1 中央事件總線

我們可以創(chuàng)建一個空的 Vue 實例作為中央事件總線创淡,實現(xiàn)非父子組件之間的通信。

html:

<div id="app5">
    <p>監(jiān)聽子組件消息:{{message}}</p>
    <deniro-component5></deniro-component5>
</div>

js:

var bus = new Vue();
Vue.component('deniro-component5', {
    template: '<button @click="sendMessage">發(fā)送消息</button>',
    methods: {
        sendMessage: function () {
            bus.$emit('on-message', '來自于 deniro-component5 的消息');
        }
    }
});
var app5 = new Vue({
    el: "#app5",
    data: {
        message: ''
    },
    mounted: function () {
        var that = this;

        bus.$on('on-message', function (message) {
            that.message = message;
        })
    }
});

注意: 因為 bus.$on() 中的函數(shù)南吮,this 指向的是本身琳彩,所以我們必須在外層定義一個 that,讓它引用 mounted 對象部凑。

效果:

首先創(chuàng)建了一個空的 Vue 實例作為中央事件總線露乏。然后在定義的子組件綁定的 click 事件中,通過 bus.$emit() 發(fā)送消息涂邀。接著在初始化 app 實例的 mounted 函數(shù)時瘟仿,使用 bus.$on() 方法監(jiān)聽消息。

這種方式可以實現(xiàn)組件間任意通信比勉。我們還可以擴展 bus 實例劳较,為它添加 data、methods浩聋、computed 等屬性观蜗,這些都是公共屬性,可以共用衣洁。所以在此可以放置需要共享的信息嫂便,比如用戶登陸昵稱等。使用時只需要初始化一次 bus 即可闸与,所以在單頁面富客戶端中應用廣泛毙替。

如果項目較大岸售,那么可以使用具有狀態(tài)管理的 vuex 哦O(∩_∩)O~

3.2 父子鏈

子組件可以使用 this.$parent 來訪問父組件實例;而父組件可以使用 this.$children 來訪問它的所有子組件實例厂画。這些方法可以遞歸向上或向下凸丸,直到根實例或者葉子實例。

html:

<div id="app6">
    <p>消息:{{message}}</p>
    <deniro-component6></deniro-component6>
</div>

js:

Vue.component('deniro-component6', {
    template: '<button @click="sendMessage">發(fā)送消息</button>',
    methods: {
        sendMessage: function () {
            //通過父鏈找到父組件袱院,修改相應的變量
            this.$parent.message='來自于 deniro-component6 的消息';
        }
    }
});
var app6 = new Vue({
    el: "#app6",
    data: {
        message: ''
    }
});

效果:

注意:只有在萬不得已的情況下屎慢,才使用父子鏈,實現(xiàn)組件間任意通信忽洛。因為這樣做腻惠,會讓兩個組件之間緊耦合,代碼變得難理解與維護欲虚。如果只是父子組件之間的通信集灌,盡量采用 props 與自定義事件 $emit 來實現(xiàn)。

3.3 子組件索引

如果一個組件的子組件較多且是動態(tài)渲染的場景复哆,使用 this.$children 來遍歷這些子組件較麻煩欣喧。這時就可以使用 ref 來為子組件指定索引名稱,方便后續(xù)查找梯找。

html:

<div id="app7">
    <button @click="getChild">獲取子組件實例</button>
    <deniro-component7 ref="child"></deniro-component7>
</div>

js:

Vue.component('deniro-component7', {
    template: '<div>deniro-component7</div>',
    data: function () {
        return {
            message: '登陸不到兩周唆阿,InSight探測器意外捕捉到火星的風聲'
        }
    }
});
var app7 = new Vue({
    el: "#app7",
    methods: {
        getChild: function () {
            //使用 $refs 來訪問組件實例
            console.log(this.$refs.child.message);
        }
    }
});

輸出結(jié)果:

登陸不到兩周,InSight探測器意外捕捉到火星的風聲

注意:$refs 只在組件渲染完成后才會被賦值锈锤,而且它是非響應式的驯鳖。所以只有在萬不得已的情況下才使用它。

本文示例代碼


總結(jié)如下:

通信方式 通信方向
props【推薦】 父組件到子組件
自定義事件 $emit【推薦】 子組件到父組件
中央事件總線【推薦】 組件間任意通信
父子鏈 組件間任意通信
子組件索引 父組件到子組件
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末久免,一起剝皮案震驚了整個濱河市浅辙,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌妄壶,老刑警劉巖摔握,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異丁寄,居然都是意外死亡氨淌,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進店門伊磺,熙熙樓的掌柜王于貴愁眉苦臉地迎上來盛正,“玉大人,你說我怎么就攤上這事屑埋『荔荩” “怎么了?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長续崖。 經(jīng)常有香客問我敲街,道長,這世上最難降的妖魔是什么严望? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任多艇,我火速辦了婚禮,結(jié)果婚禮上像吻,老公的妹妹穿的比我還像新娘峻黍。我一直安慰自己,他們只是感情好拨匆,可當我...
    茶點故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布姆涩。 她就那樣靜靜地躺著,像睡著了一般惭每。 火紅的嫁衣襯著肌膚如雪骨饿。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天洪鸭,我揣著相機與錄音样刷,去河邊找鬼仑扑。 笑死览爵,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的镇饮。 我是一名探鬼主播蜓竹,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼储藐!你這毒婦竟也來了俱济?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤钙勃,失蹤者是張志新(化名)和其女友劉穎蛛碌,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體辖源,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡蔚携,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了克饶。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片酝蜒。...
    茶點故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖矾湃,靈堂內(nèi)的尸體忽然破棺而出亡脑,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布霉咨,位于F島的核電站蛙紫,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏途戒。R本人自食惡果不足惜惊来,卻給世界環(huán)境...
    茶點故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望棺滞。 院中可真熱鬧裁蚁,春花似錦、人聲如沸继准。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽移必。三九已至室谚,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間崔泵,已是汗流浹背秒赤。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留憎瘸,地道東北人入篮。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像幌甘,于是被迫代替她去往敵國和親潮售。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,713評論 2 354

推薦閱讀更多精彩內(nèi)容

  • 組件(Component)是Vue.js最核心的功能锅风,也是整個架構(gòu)設計最精彩的地方酥诽,當然也是最難掌握的。...
    六個周閱讀 5,604評論 0 32
  • 前言 您將在本文當中了解到,往網(wǎng)頁中添加數(shù)據(jù),從傳統(tǒng)的dom操作過渡到數(shù)據(jù)層操作,實現(xiàn)同一個目標,兩種不同的方式....
    itclanCoder閱讀 25,797評論 1 12
  • 這篇筆記主要包含 Vue 2 不同于 Vue 1 或者特有的內(nèi)容皱埠,還有我對于 Vue 1.0 印象不深的內(nèi)容肮帐。關于...
    云之外閱讀 5,050評論 0 29
  • 什么是組件? 組件 (Component) 是 Vue.js 最強大的功能之一边器。組件可以擴展 HTML 元素训枢,封裝...
    youins閱讀 9,480評論 0 13
  • 今天是來到三亞的第二天。早上六點半起床饰抒,開始5公里的海岸線慢跑肮砾。完成打卡。 大閨蜜小閨蜜都在三亞袋坑,趁上午不開營仗处,打...
    盛藍閱讀 234評論 3 2