本想簡單寫寫,沒想到說清楚已經變成了一篇很長的帖子,歡迎當筆記搜藏起來。
props / emits 父子組件通信
props一般負責向子組件傳遞數據
下面是一個簡單的例子,父組件向子組件傳遞了一個title,子組件負責顯示title。
// child-component.vue
<template>
<h2>{{ title }}</h2>
</template>
<script>
export default {
name: "child-component",
props: ['title'] //可以注冊多個prop
}
</script>
// 父組件中,使用子組件
<ChildComponent :title="'這是傳遞的Title'"></ChildComponent>
emits主要用於監聽子組件事件
開發過程中子組件可能需要與父級組件進行溝通,這時我們就需要用到emits。下面是一個簡單的例子,點擊子組件按鈕時,改變了父組件顯示的信息。
// child-component.vue
<button @click="$emit('textChange', '子組件被點擊了')">點我</button>
// 父組件中,使用子組件
<template>
<div>
<div>{{ msg }}</div>
<ChildComponent @textChange="msg = $event"></ChildComponent>
</div>
</template>
<script>
import ChildComponent from "./child-component"
export default {
components: {
ChildComponent
},
data() {
return {
msg: 'test msg!'
}
}
}
</script>
prop 類型、驗證以及默認值
這三塊內容在官網篇幅還挺長的,感覺上重點卻不多,我們上面注冊props的時候使用的數組,如:["title"],其實實際開發過程中,為了傳值的清晰,我們用的都是對象,此外我們會指定其類型,驗證規則,以及給出默認值,三者都是可選的,看下面的例子:
export default {
props: {
// 基礎的類型檢查 (`null` 和 `undefined` 會通過任何類型驗證)
propA: Number,
// 多個可能的類型
propB: [String, Number],
// 必填的字符串
propC: {
type: String,
required: true
},
// 帶有默認值的數字
propD: {
type: Number,
default: 100
},
// 帶有默認值的對象
propE: {
type: Object,
// 對象或數組默認值必須從一個工廠函數獲取
default() {
return { message: 'hello' }
}
},
// 自定義驗證函數
propF: {
validator(value) {
// 這個值必須匹配下列字符串中的一個
return ['success', 'warning', 'danger'].includes(value)
}
},
// 具有默認值的函數
propG: {
type: Function,
// 與對象或數組默認值不同,這不是一個工廠函數 —— 這是一個用作默認值的函數
default() {
return 'Default function'
}
}
}
}
注意:
所有的 prop 都使得其父子 prop 之間形成了一個單向下行綁定:父級 prop 的更新會向下流動到子組件中,但是反過來則不行。這樣會防止從子組件意外變更父級組件的狀態,從而導致你的應用的數據流向難以理解。
另外,每次父級組件發生變更時,子組件中所有的 prop 都將會刷新為最新的值。這意味着你不應該在一個子組件內部改變 prop。如果你這樣做了,Vue 會在瀏覽器的控制台中發出警告。
但是,如果prop傳遞的是一個對象或者數組,我們是可以改變其內部的數據的,同時這個改變會變更對象或數組本身,從而影響到其所在父組件的狀態。
vue3的新選項emits
vue 3 提供了一個 emits 選項,和現有的 props 選項類似。這個選項可以用來定義一個組件可以向其父組件觸發的事件。emits 選項中列出的事件不會從組件的根元素繼承,也將從$attrs property中移除。
emits 可以是數組或對象,從組件觸發自定義事件,emits 可以是簡單的數組,也可以是對象,后者允許配置事件驗證(因為返回false也依舊會繼續調用事件,所以作者本人沒理解驗證這塊,有知道的小伙伴歡迎留言)。使用如下:
emits: {
// 沒有驗證函數
click: null,
// 帶有驗證函數
submit: payload => {
if (payload.email && payload.password) {
return true
} else {
return false // 返回false事件依舊會繼續被調用
}
}
}
因為不聲明emits也可以使$emit來調用傳遞的事件,所以看似用處不大,但聲明首先可以使代碼清晰,可以一眼看到向其父組件透傳的事件。同時需要向其父組件透傳原生事件(如:@ckick)的組件來說,不聲明會導致事件被觸發兩次,而聲明可以避免這類問題。
provide / inject 多層嵌套組件通信
使用場景:
1、多層嵌套組件傳值
2、父子組件相互尋找比較麻煩,如slot

簡單示例:
// 父組件 提供對象
provide: {
user: 'John Doe'
}
// 子組件,接收對象
inject: ['user'],
created() {
console.log(`Injected property: ${this.user}`) // > 注入 property: John Doe
}
訪問組件實例 property(用this),我們需要將 provide 轉換為返回對象的函數
// 父組件
data() {
return {
userName: 'test name',
}
},
provide() {
return {
user: this.userName
}
}
響應性注意這里傳遞的是值,並不是響應式的,官網建議分配一個組合式 API computed property:
// 來自官網的建議
todoLength: Vue.computed(() => this.todos.length)
個人建議直接傳遞一個對象過去,利用指針傳遞來實現響應式。(簡單粗暴)如果非要了解可以關注后續再寫文章中關於 reactive provide/inject 的信息
$attrs / $parent / $refs 能抓到貓就是好老鼠系列
$attrs 包含了父作用域中不作為組件 props 或 emits 的 attribute 綁定和事件。當一個組件沒有聲明任何 prop 時,這里會包含所有父作用域的綁定,並且可以通過 v-bind="$attrs" 傳入內部組件——這在創建高階的組件時會非常有用。
示例:
// 父組件中
<ChildComponent title="123" titleProp="456"></ChildComponent>
// 子組件中
props: ['titleProp'],
created() {
console.log(this.$attrs.title); // 123
console.log(this.$attrs.titleProp); // undefined
console.log(this.title); // undefined
console.log(this.titleProp); // 123
}
$parent 這是父實例,如果當前實例有的話。簡單粗暴拿着實例各種用。
$refs 一個對象,持有注冊過ref的所有 DOM 元素和組件實例。父組件使用其可以輕易的獲取子組件,並各種使用。
eventBus 事件總線vue3不支持了
在絕大多數情況下,不鼓勵使用全局的事件總線在組件之間進行通信。雖然在短期內往往是最簡單的解決方案,但從長期來看,它維護起來總是令人頭疼。
如果非得要用,可以使用一些外部第三方庫,例如 mitt 或 tiny-emitter
// eventBus.js
import emitter from 'tiny-emitter/instance'
export default {
$on: (...args) => emitter.on(...args),
$once: (...args) => emitter.once(...args),
$off: (...args) => emitter.off(...args),
$emit: (...args) => emitter.emit(...args),
}
vuex 和 自定義共享對象
vuex 輕松獲取響應式的全局數據。
業務獨立的組件通信更推薦的做法是自定義一個js對象,用js對象來保存共享數據和函數。export是一個對象的話,import之后訪問的是同一個對象,其可以方便的提供數據共享和函數調用。此外,如果想要不同的對象可以export一個工廠函數。
總結
- props / emits 應該是父子組件之間溝通的首選。兄弟節點可以通過它們的父節點通信。
- provide / inject 允許一個組件與它的插槽內容進行通信。這對於總是一起使用的緊密耦合的組件非常有用。
- provide / inject 也能夠用於組件之間的遠距離通信。它可以幫助避免“prop 逐級透傳”,即 prop 需要通過許多層級的組件傳遞下去,但這些組件本身可能並不需要那些 prop。
- prop 逐級透傳也可以通過重構以使用插槽來避免。如果一個中間組件不需要某些 prop,那么表明它可能存在關注點分離的問題。在該類組件中使用 slot 可以允許父節點直接為它創建內容,因此 prop 可以被直接傳遞而不需要中間組件的參與。
- 全局狀態管理,比如 Vuex
