組件是現代前端框架最強大的功能,但是組件的作用域是相互獨立的,那麼不同組件之間如何傳遞數據呢?
為了構建大型 Web 應用,現代前端框架都引入了組件系統,將模版和邏輯抽象為可重用的組件,可以提高項目的開發效率和可維護性。而組件間的通信一直是一個老生常談的問題,今天來討論一下 Vue 中的組件通信。
props#
通過 props 可以實現父組件向子組件傳遞數據,prop 是定義在組件上的一些自定義屬性,需要提前在子組件中的 props 聲明。
// 子組件
<template>
<div>{{text}}</div>
</template>
<script>
export default {
name: 'Confirm',
props: {
text: {
type: String,
default: ''
},
}
}
</script>
// 父組件
<template>
<confirm :text="文字">
</template>
自定義事件#
通過自定義事件的方式可以在子組件中向父組件傳遞數據。
// 子組件
<template>
<button @click="confirm">Click</button>
</template>
<script>
export default {
name: 'Confirm',
methods: {
confirm() {
this.$emit('confirm',"data")
}
}
}
</script>
// 父組件
<template>
<confirm @confirm="handleConfirm">
</template>
<script>
export default {
methods: {
confirm(data) {
console.log(data);
}
}
}
</script>
子組件使用 this.$emit 可以觸發自定義事件,第一個參數是自定義事件名稱,第二個參數為傳遞的參數(可選)。父組件使用 @監聽子組件的自定義事件。
事件總線#
props 只適用於父子組件之間數據的傳遞,兄弟組件之間的通信可以使用 eventBus 的方式,具體使用方式如下:
新建 eventBus.js:
import Vue from 'vue';
export default new Vue();
觸發事件的組件:
import eventBus from './eventBus.js';
methods: {
onClick(event) {
eventBus.$emit('clickEvent', event.target);
}
}
監聽事件的組件:
import eventBus from './eventBus.js';
methods: {
onClick(event) {
eventBus.$on('clickEvent', (data) => {
console.log(data);
});
}
}
eventBus 的方法實現了一個全局的狀態管理,在任意組件之間都能非常便捷的傳遞數據。
Vuex#
在大型 Web 應用中,上述組件通信方式已經不能很好地滿足實際的需求,Vue 官網提供了一個全局狀態管理庫:Vuex。具體可參考官方文檔。
$root 和 $parent#
在每個 new Vue 實例的子組件中,其根實例可以通過 $root property 進行訪問。
所有的子組件都可以將這個實例作為一個全局 store 來訪問或使用。
和 $root 類似,$parent 可以用來從一個子組件訪問父組件的實例。因此可以利用 $root 或者 $root 作為橋梁在子組件之間傳遞數據。
觸發事件的組件:
methods: {
onClick(event) {
this.$root.$emit('clickEvent', event.target);
}
}
監聽事件的組件:
methods: {
onClick(event) {
this.$root.$on('clickEvent', (data) => {
console.log(data);
});
}
}
$children#
父組件也可以使用 $children 來訪問子組件實現父子組件的通信。
this.$children 是當前實例的直接子組件。需要注意 $children 並不保證順序,也不是響應式的。
慎重使用 $parent 和 $children,它們的主要目的是作為訪問組件的應急方法。更推薦用 props 和 events 實現父子組件通信。
//父組件
this.$children[0].xx = xx;
$refs#
$refs 可以創建一個子元素或者子組件的引用,使用 $refs 可以很方便的調用子組件的方法或者獲取子組件的數據。
// 父組件
<template>
<base-input ref="usernameInput"></base-input>
</template>
<script>
export default {
methods: {
focus() {
this.$refs.usernameInput.focus();
}
}
}
</script>
// 子組件
<template>
<input ref="input">
</template>
<script>
export default {
methods: {
focus: function () {
this.$refs.input.focus()
}
}
}
</script>
$attrs 和 $listeners#
$attrs 中包含了父組件傳入子組件,且未在子組件 props 中定義的屬性(class 和 style 除外),$attrs 會被自動添加到子組件的根元素上,如果你不希望組件的根元素繼承 attribute,你可以在組件的選項中設置 inheritAttrs: false。
//子組件
Vue.component('base-input', {
inheritAttrs: false,
props: ['label', 'value'],
template: `
<label>
{{ label }}
<input
v-bind="$attrs"
v-bind:value="value"
v-on:input="$emit('input', $event.target.value)"
>
</label>
`
})
//父組件
<base-input
v-model="username"
required
placeholder="Enter your username"
></base-input>
同理,$listeners 可以將父組件傳入的所有事件監聽函數指向子組件的某個特定的子元素。
Vue.component('base-input', {
inheritAttrs: false,
props: ['label', 'value'],
computed: {
inputListeners: function () {
var vm = this
return Object.assign({},
{
input: function (event) {
vm.$emit('input', event.target.value)
}
}
)
}
},
template: `
<label>
{{ label }}
<input
v-bind="$attrs"
v-bind:value="value"
v-on="inputListeners"
>
</label>
`
})
provide/inject#
provide 和 inject 是 Vue 中依賴注入的實現,provide 選項允許我們指定我們想要提供給後代組件的數據 / 方法。
// 提供數據的組件
<script>
export default {
provide() {
return {
form: this
};
},
}
</script>
然後在任何後代組件裡,我們都可以使用 inject 選項來接收指定的我們想要添加在組件上的屬性:
<script>
export default {
inject: ["form"],
methods: {
validate() {
console.log(this.form);
}
}
</script>
Vue 官方的狀態管理庫 - Vuex 的原理也是使用了 provide/inject。
總結#
上述就是 Vue 中的組件通信方案,不同的方案適用場景不同,沒有銀彈,在項目開發中,需要根據項目需求來選擇適合的方案。