Vue.js 父子組件之間通信的十種方式

這篇文章介紹了Vue.js 父子組件之間通信的十種方式灵再,不管是初學(xué)者還是已經(jīng)在用 Vue 的開發(fā)者都會(huì)有所收獲扰魂。無可否認(rèn)咕幻,現(xiàn)在無論大廠還是小廠都已經(jīng)用上了 Vue.js 框架灾测,簡單易上手不說钧舌,教程詳盡流椒,社區(qū)活躍敏簿,第三方套件還多。真的是前端開發(fā)人員必備技能宣虾。而且在面試當(dāng)中也往往會(huì)問到關(guān)于 Vue 方面的各種問題惯裕,其中大部分面試官會(huì)問到如上這種問題。

概述

幾種通信方式無外乎以下幾種:

  • Prop(常用)
  • $emit (組件封裝用的較多)
  • .sync語法糖 (較少)
  • $attrs$listeners (組件封裝用的較多)
  • provideinject (高階組件/組件庫用的較多)
  • 其他方式通信

詳述

下面逐個(gè)介紹绣硝,大神請(qǐng)繞行蜻势。

1. Prop

英式發(fā)音:[pr?p]。這個(gè)在我們?nèi)粘i_發(fā)當(dāng)中用到的非常多鹉胖。簡單來說握玛,我們可以通過 Prop 向子組件傳遞數(shù)據(jù)。用一個(gè)形象的比喻來說甫菠,父子組件之間的數(shù)據(jù)傳遞相當(dāng)于自上而下的下水管子挠铲,只能從上往下流,不能逆流寂诱。這也正是 Vue 的設(shè)計(jì)理念之單向數(shù)據(jù)流拂苹。而 Prop 正是管道與管道之間的一個(gè)銜接口,這樣水(數(shù)據(jù))才能往下流刹衫。說這么多醋寝,看代碼:

<``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!瀏覽器輸出:

<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;">1 from parent</pre>

2. $emit

英式發(fā)音:[i?m?t]带迟。官方說法是觸發(fā)當(dāng)前實(shí)例上的事件音羞。附加參數(shù)都會(huì)傳給監(jiān)聽器回調(diào)。按照我的理解不知道能不能給大家說明白仓犬,先簡單看下代碼吧:

<``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! 大致邏輯是醬嬸兒的:當(dāng)我在頁面上點(diǎn)擊按鈕時(shí)嗅绰,觸發(fā)了組件 MyButton 上的監(jiān)聽事件 greet,并且把參數(shù)傳給了回調(diào)函數(shù) sayHi 搀继。說白了窘面,當(dāng)我們從子組件 Emit(派發(fā)) 一個(gè)事件之前,其內(nèi)部都提前在事件隊(duì)列中 On(監(jiān)聽)了這個(gè)事件及其監(jiān)聽回調(diào)叽躯。其實(shí)相當(dāng)于下面這種寫法:

vm.$on(``'greet'``, ``function sayHi (val) {

console.log(``'Hi, ' + val)

}

vm.$emit(``'greet'``, ``'vue.js'``)

// => "Hi, vue.js"

|

3. .sync 修飾符

這個(gè)家伙在 vue@1.x 的時(shí)候曾作為雙向綁定功能存在财边,即子組件可以修改父組件中的值。因?yàn)樗`反了單向數(shù)據(jù)流的設(shè)計(jì)理念点骑,所以在 vue@2.0 的時(shí)候被干掉了酣难。但是在 vue@2.3.0+ 以上版本又重新引入了這個(gè) .sync 修飾符谍夭。但是這次它只是作為一個(gè)編譯時(shí)的語法糖存在。它會(huì)被擴(kuò)展為一個(gè)自動(dòng)更新父組件屬性的 v-on 監(jiān)聽器憨募。說白了就是讓我們手動(dòng)進(jìn)行更新父組件中的值了紧索,從而使數(shù)據(jù)改動(dòng)來源更加的明顯。下面引入自官方的一段話:

在有些情況下菜谣,我們可能需要對(duì)一個(gè) prop 進(jìn)行“雙向綁定”珠漂。不幸的是,真正的雙向綁定會(huì)帶來維護(hù)上的問題尾膊,因?yàn)樽咏M件可以修改父組件媳危,且在父組件和子組件都沒有明顯的改動(dòng)來源。

既然作為一個(gè)語法糖眯停,肯定是某種寫法的簡寫形式济舆,哪種寫法呢,看代碼:

<``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``>

|

廢話這么多莺债,如何做到“雙向綁定” 呢滋觉?讓我們進(jìn)段廣告,廣告之后更加精彩齐邦! ... 好的椎侠,歡迎回來。假如我們想實(shí)現(xiàn)這樣一個(gè)效果:改變子組件文本框中的值同時(shí)改變父組件中的值措拇。怎么做我纪?列位不妨先想想。先看段代碼:

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!下面劃重點(diǎn)丐吓,代碼里有這一句話:

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

官方語法是:update:myPropName 其中 myPropName 表示要更新的 prop 值浅悉。當(dāng)然如果你不用 .sync 語法糖使用上面的 .$emit 也能達(dá)到同樣的效果。僅此而已券犁!

4. $attrs$listeners

  • 官網(wǎng)對(duì) $attrs 的解釋如下:

包含了父作用域中不作為 prop 被識(shí)別 (且獲取) 的特性綁定 (classstyle 除外)术健。當(dāng)一個(gè)組件沒有聲明任何 prop 時(shí),這里會(huì)包含所有父作用域的綁定 (classstyle 除外)粘衬,并且可以通過 v-bind="$attrs" 傳入內(nèi)部組件——在創(chuàng)建高級(jí)別的組件時(shí)非常有用荞估。

  • 官網(wǎng)對(duì) $listeners 的解釋如下:

包含了父作用域中的 (不含 .native 修飾器的) v-on 事件監(jiān)聽器。它可以通過 v-on="$listeners" 傳入內(nèi)部組件——在創(chuàng)建更高層次的組件時(shí)非常有用稚新。

我覺得 $attrs$listeners 屬性像兩個(gè)收納箱勘伺,一個(gè)負(fù)責(zé)收納屬性,一個(gè)負(fù)責(zé)收納事件褂删,都是以對(duì)象的形式來保存數(shù)據(jù)飞醉。看下面的代碼解釋:

<``div id="app">

<``child

:foo="foo"

:bar="bar"

@one.native="triggerOne"

@two="triggerTwo">

</``child

</div>

|

從 Html 中可以看到屯阀,這里有倆屬性和倆方法冒掌,區(qū)別是屬性一個(gè)是 prop 聲明噪裕,事件一個(gè)是 .native 修飾器蹲盘。

|

let Child = Vue.extend({

template: ``'<h2>{{ foo }}</h2>'``,

props: [``'foo'``],

created () {

console.log(``this``.$attrs, ``this``.$listeners)

// -> {bar: "parent bar"}

// -> {two: fn}

// 這里我們?cè)L問父組件中的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 進(jìn)行數(shù)據(jù)傳遞,在需要的地方進(jìn)行調(diào)用和處理召衔,還是很方便的铃诬。當(dāng)然,我們還可以通過 v-on="$listeners" 一級(jí)級(jí)的往下傳遞苍凛,子子孫孫無窮盡也趣席!

一個(gè)插曲!

當(dāng)我們?cè)诮M件上賦予了一個(gè)非Prop 聲明時(shí)醇蝴,編譯之后的代碼會(huì)把這些個(gè)屬性都當(dāng)成原始屬性對(duì)待宣肚,添加到 html 原生標(biāo)簽上,看上面的代碼編譯之后的樣子:

|

1

|

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

|

這樣會(huì)很難看悠栓,同時(shí)也爆了某些東西霉涨。如何去掉?這正是 inheritAttrs 屬性的用武之地惭适!給組件加上這個(gè)屬性就行了笙瑟,一般是配合 $attrs 使用●荆看代碼:

// 源碼

let Child = Vue.extend({

...

inheritAttrs: ``false``, ``// 默認(rèn)是 true

...

})

|

再次編譯:

|

1

|

<``h2``>parent foo</``h2``>

|

5. provide / inject

他倆是對(duì)CP, 感覺挺神秘的往枷。來看下官方對(duì) provide / inject 的描述:

provideinject 主要為高階插件/組件庫提供用例。并不推薦直接用于應(yīng)用程序代碼中凄杯。并且這對(duì)選項(xiàng)需要一起使用错洁,以允許一個(gè)祖先組件向其所有子孫后代注入一個(gè)依賴,不論組件層次有多深戒突,并在起上下游關(guān)系成立的時(shí)間里始終生效屯碴。

看完描述有點(diǎn)懵懵懂懂!一句話總結(jié)就是:小時(shí)候你老爸什么東西都先幫你存著等你長大該娶媳婦兒了你要房子給你買要車給你買只要他有的盡量都會(huì)滿足你妖谴。下面是這句話的代碼解釋:

<``div id="app">

<``son``></``son``>

</``div``>

let Son = Vue.extend({

template: ``'<h2>son</h2>'``,

inject: {

house: {

default``: ``'沒房'

},

car: {

default``: ``'沒車'

},

money: {

// 長大工作了雖然有點(diǎn)錢

// 僅供生活費(fèi)窿锉,需要向父母要

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. 其他方式通信

除了以上五種方式外,其實(shí)還有:

  • EventBus

思路就是聲明一個(gè)全局Vue實(shí)例變量 EventBus , 把所有的通信數(shù)據(jù)膝舅,事件監(jiān)聽都存儲(chǔ)到這個(gè)變量上嗡载。這樣就達(dá)到在組件間數(shù)據(jù)共享了,有點(diǎn)類似于 Vuex仍稀。但這種方式只適用于極小的項(xiàng)目洼滚,復(fù)雜項(xiàng)目還是推薦 Vuex。下面是實(shí)現(xiàn) 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'

// 事件監(jiān)聽

EventBus.$on(``'received'``, ``function (val) {

console.log(``'received: '``+ val)

// -> 'received: from child'

})

}

})

|

你可以狠狠的戳這里查看Demo!

  • Vuex

官方推薦的技潘,Vuex 是一個(gè)專為 Vue.js 應(yīng)用程序開發(fā)的狀態(tài)管理模式遥巴。

  • $parent

父實(shí)例千康,如果當(dāng)前實(shí)例有的話。通過訪問父實(shí)例也能進(jìn)行數(shù)據(jù)之間的交互铲掐,但極小情況下會(huì)直接修改父組件中的數(shù)據(jù)拾弃。

  • $root

當(dāng)前組件樹的根 Vue 實(shí)例。如果當(dāng)前實(shí)例沒有父實(shí)例摆霉,此實(shí)例將會(huì)是其自己豪椿。通過訪問根組件也能進(jìn)行數(shù)據(jù)之間的交互,但極小情況下會(huì)直接修改父組件中的數(shù)據(jù)携栋。

  • broadcast / dispatch

他倆是 vue@1.0 中的方法搭盾,分別是事件廣播 和 事件派發(fā)。雖然 vue@2.0 里面刪掉了婉支,但可以模擬這兩個(gè)方法鸯隅。可以借鑒 Element 實(shí)現(xiàn)向挖。有時(shí)候還是非常有用的蝌以,比如我們?cè)陂_發(fā)樹形組件的時(shí)候等等。

《作者:gongph户誓,鏈接:https://juejin.im/post/5bd18c72e51d455e3f6e4334

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末饼灿,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子帝美,更是在濱河造成了極大的恐慌碍彭,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件悼潭,死亡現(xiàn)場離奇詭異庇忌,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)舰褪,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門皆疹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人占拍,你說我怎么就攤上這事略就。” “怎么了晃酒?”我有些...
    開封第一講書人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵表牢,是天一觀的道長。 經(jīng)常有香客問我贝次,道長崔兴,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮敲茄,結(jié)果婚禮上位谋,老公的妹妹穿的比我還像新娘。我一直安慰自己堰燎,他們只是感情好掏父,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著爽待,像睡著了一般损同。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上鸟款,一...
    開封第一講書人閱讀 51,631評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音茂卦,去河邊找鬼何什。 笑死,一個(gè)胖子當(dāng)著我的面吹牛等龙,可吹牛的內(nèi)容都是我干的处渣。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼蛛砰,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼罐栈!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起泥畅,我...
    開封第一講書人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤荠诬,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后位仁,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體柑贞,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年聂抢,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了钧嘶。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡琳疏,死狀恐怖有决,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情空盼,我是刑警寧澤书幕,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站我注,受9級(jí)特大地震影響按咒,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一励七、第九天 我趴在偏房一處隱蔽的房頂上張望智袭。 院中可真熱鬧,春花似錦掠抬、人聲如沸吼野。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽瞳步。三九已至,卻和暖如春腰奋,著一層夾襖步出監(jiān)牢的瞬間单起,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來泰國打工劣坊, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留嘀倒,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓局冰,卻偏偏與公主長得像测蘑,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子康二,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355

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