之前說過捏萍,可以使用 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 組件具有以下特征:
- 使用 props 接收父組件的 value倒彰。
- 子組件中擁有可以更新 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 【推薦】 |
子組件到父組件 |
中央事件總線【推薦】 | 組件間任意通信 |
父子鏈 | 組件間任意通信 |
子組件索引 | 父組件到子組件 |