之前說過,可以使用 props 將資料從父元件傳遞給子元件。其實還有其它種的通訊方式,下面我們一一娓娓道來。
1 自定義事件
通過自定義事件,我們可以把資料從子元件傳輸回父元件。子元件通過 $emit()
來觸發事件,而父元件通過 $on()
來監聽事件,這是典型的觀察者模式。
html:
<div id="app">
<p>總數:{{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;
}
}
});
複製程式碼
效果:
示例中有兩個按鈕,分別實現加 1 與減 1 操作。點選按鈕後,執行元件中定義的 increase 或 reduce 方法,在方法內部,使用 $emit
把值傳遞迴父元件。 $emit
方法的第一個引數是使用元件時定義的事件名,示例中是 @increase
與 @reduce
:
<deniro-component @increase="setTotal"
@reduce="setTotal"
></deniro-component>
複製程式碼
這兩個事件又繫結了 setTotal
方法,該方法修改了 total 值。 $emit
方法的其它引數是需要回傳給父元件的引數。
也可以使用 v-on
加 .native
來監聽原生事件,比如這裡監聽元件的點選事件:
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~
**注意:**這裡監聽的是這個元件根元素的原生點選事件。
2 v-model 方式
也可以使用 v-model 方式來直接繫結父元件變數,把資料從子元件傳回父元件。
html:
<div id="app2">
<p>總數:{{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~
我們也可以使用自定義事件來實現上述示例——
html:
<div id="app3">
<p>總數:{{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,實現資料雙向繫結:
html:
<div id="app4">
<p>總數:{{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 變數。然後在元件內部,定義了 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和自定義事件,實現了資料的雙向繫結。
總的來說,一個具有雙向繫結的 v-model 元件具有以下特徵:
- 使用 props 接收父元件的 value。
- 子元件中擁有可以更新 value 的 HTML 元素,當更新 value 時,觸發 input 事件。事件內部使用
$emit
將新的 value 值回傳給父元件。
3 非父子元件
非父子元件指的是兄弟元件或者跨多級元件。
3.1 中央事件匯流排
我們可以建立一個空的 Vue 例項作為中央事件匯流排,實現非父子元件之間的通訊。
html:
<div id="app5">
<p>監聽子元件訊息:{{message}}</p>
<deniro-component5></deniro-component5>
</div>
複製程式碼
js:
var bus = new Vue();
Vue.component('deniro-component5', {
template: '<button @click="sendMessage">傳送訊息</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()
中的函式,this 指向的是本身,所以我們必須在外層定義一個 that,讓它引用 mounted 物件。
效果:
首先建立了一個空的 Vue 例項作為中央事件匯流排。然後在定義的子元件繫結的 click 事件中,通過 bus.$emit()
傳送訊息。接著在初始化 app 例項的 mounted
函式時,使用 bus.$on()
方法監聽訊息。
這種方式可以實現元件間任意通訊。我們還可以擴充套件 bus 例項,為它新增 data、methods、computed 等屬性,這些都是公共屬性,可以共用。所以在此可以放置需要共享的資訊,比如使用者登陸暱稱等。使用時只需要初始化一次 bus 即可,所以在單頁面富客戶端中應用廣泛。
如果專案較大,那麼可以使用具有狀態管理的 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">傳送訊息</button>',
methods: {
sendMessage: function () {
//通過父鏈找到父元件,修改相應的變數
this.$parent.message='來自於 deniro-component6 的訊息';
}
}
});
var app6 = new Vue({
el: "#app6",
data: {
message: ''
}
});
複製程式碼
效果:
**注意:**只有在萬不得已的情況下,才使用父子鏈,實現元件間任意通訊。因為這樣做,會讓兩個元件之間緊耦合,程式碼變得難理解與維護。如果只是父子元件之間的通訊,儘量採用 props
與自定義事件 $emit
來實現。
3.3 子元件索引
如果一個元件的子元件較多且是動態渲染的場景,使用 this.$children
來遍歷這些子元件較麻煩。這時就可以使用 ref
來為子元件指定索引名稱,方便後續查詢。
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);
}
}
});
複製程式碼
輸出結果:
登陸不到兩週,InSight探測器意外捕捉到火星的風聲
注意:$refs
只在元件渲染完成後才會被賦值,而且它是非響應式的。所以只有在萬不得已的情況下才使用它。
總結如下:
通訊方式 | 通訊方向 |
---|---|
props 【推薦】 |
父元件到子元件 |
自定義事件 $emit 【推薦】 |
子元件到父元件 |
中央事件匯流排【推薦】 | 元件間任意通訊 |
父子鏈 | 元件間任意通訊 |
子元件索引 | 父元件到子元件 |