文章目錄
- 什么是組件通信?
- 父子通信流程
- props
- Props 定義
- Props 作用
- 特點
- 數組寫法
- 對象寫法(props校驗)
- 簡寫只驗證數據類型:
- 完整寫法,完整的驗證:
- props父向子傳值
- 用`props`父傳子
- 在子組件中修改`props`
- $emit子向父傳值
- 用`props`子傳父
- 用`$emit`子傳父
- 事件總線
- 事件總線前置知識
- `$on`
- `$off`
- 事件總線的使用
- 父通過ref、$refs向子拿值
- 父子通信`$children` /`$parent`
- $children
- 示例
- $parent
- 示例
- 注意事項
- 跨層級通信`provide/inject`
- 跨層級通信`attrs/listeners`
- vuex
什么是組件通信?
組件通信,就是指組件與組件之間的數據傳遞
- 組件的數據是獨立的,無法直接訪問其他組件的數據
- 想使用其他組件的數據,就需要組件通信
父子通信流程
- 父組件通過 props 將數據傳遞給子組件
- 子組件利用 $emit 通知父組件修改更新
props
Props 定義
組件上 注冊的一些 自定義屬性
Props 作用
-
向子組件傳遞數據
-
props接受的數據,只讀,不可修改
-
props接收到的簡單數據類型,不可修改
-
props接收到的復雜數據類型,可修改,vue也不會報錯,但是不建議這樣做
特點
- 可以 傳遞 任意數量 的prop
- 可以 傳遞 任意類型 的prop
數組寫法
數組寫法沒有校驗,所以不會在控制臺報錯
props: ['title','text', 'name']
對象寫法(props校驗)
作用:
- 為組件的 prop 指定驗證要求,不符合要求,控制臺就會有錯誤提示
- 幫助開發者,快速發現錯誤
簡寫只驗證數據類型:
props: {校驗的屬性名: 數據類型
}
完整寫法,完整的驗證:
props: {校驗的屬性名: {type: 數據類型, // Number String Boolean ...required: true, // 是否必填default: 默認值, // 默認值(未傳值就使用默認值)validator (value) { // value接收的就是傳過來的值// 自定義校驗邏輯return 布爾值 // 返回true就代表通過驗證}}
}
示例:
<script>
export default {// 完整寫法(類型、默認值、非空、自定義校驗)props: {w: {type: Number,//required: true,default: 0,validator(val) {// console.log(val)if (val >= 100 || val <= 0) {console.error('傳入的范圍必須是0-100之間')return false} else {return true}},},},
}
</script>
props父向子傳值
用props
父傳子
- 給子組件以添加屬性的方式傳值
- 子組件內部通過props接收
- 模板中直接使用 props接收的值
在子組件中修改props
- 如果傳過來的是簡單數據類型,那么是無法修改的
- 如果傳過來的是復雜數據類型,那么是可以修改的,但是不建議去修改
- 子組件如果要修改父組件傳過來的數據的話,可以用
$emit
去觸發父組件的自定義事件,讓父組件自己去修改數據
$emit子向父傳值
用props
子傳父
- 父通過自定義屬性將自己的方法傳送出去
- 子通過props接受父傳過來的方法
- 子通過調用接收過來的方法去傳參的形式傳數據
- 因為方法是在父親身上,父親因此接收到了數據
用$emit
子傳父
- 給子組件綁定自定義事件,事件觸發時調用來自父親的回調函數
vm.$emit( eventName, […args] )
,eventName
為字符串類型,[…args]
可省略,或者傳入多條數據- 在子組件內利用
$emit
觸發子組件綁定的自定義事件,傳入數據,進行修改更新
上圖流程:
- 點擊
button
,調用回調函數changeTitle
- 回調函數調用
$emit
方法 $emit
方法會去觸發子組件綁定的自定義事件changeTitle
,同時傳入參數- 子組件綁定的自定義事件
changeTitle
被觸發后會調用回調函數handleChange
- 回調函數
handleChange
用接收到的參數去修改數據msg
事件總線
事件總線前置知識
$on
可以給當前實例添加自定義事件,并且監聽添加的自定義事件
vm.$on( event, callback )
- 在 Vue 2 中,
$on
是 Vue 實例的一個方法,監聽當前實例上的自定義事件 - 事件可以由
vm.$emit
觸發,當觸發這些事件時,可以執行相應的回調函數 - 相比于在標簽上直接寫事件,通過
$on
添加的自定義事件靈活性更強
$off
用于移除自定義事件監聽器
vm.$off( [event, callback] )
- 如果沒有提供參數,則移除所有的事件監聽器
- 如果只提供了事件,則移除該事件所有的監聽器
- 如果同時提供了事件與回調,則只移除這個回調的監聽器
事件總線的使用
事件總線可以用于任意兩個組件之間的通信
1、在src目錄下創建個utils文件夾,里面創建EventBus.js文件(事件總線)
EventBus.js
// 導入vue文件
import Vue from 'vue'
// 創建空vue實例對象
const Bus = new Vue()
// 導出空vue實例對象
export default Bus
2、接收方son1.vue
<template><div>我是son1(接收消息)</div>
</template>
<script>
import Bus from '../utils/EventBus'
export default {name: 'mySon1',created(){Bus.$on('myEvent',(msg)=>{ // 通過$on為Bus自定義事件,當事件觸發時,后面的回調函數接收信息console.log('我是son1拿到了son2的數據:',msg)})}
}
</script>
3、發送方son2.vue
<template><div>我是son2(發布消息)<button @click="sendMsg">發送消息</button></div>
</template>
<script>
import Bus from '@/utils/EventBus'
export default {name: 'mySon2',data() {return {msg: '我是son2中的數據'}},methods: {sendMsg() {Bus.$emit('myEvent', this.msg) // 通過$emit觸發事件,觸發事件的同時將數據傳出去}}
}
</script>
? Vue 的事件總線通常是通過將其定義在 Vue 的原型上來實現的,這樣任何 Vue 組件都可以通過 this.$bus
來訪問和使用它。這樣做的好處是全局事件總線可以在任何組件內部進行事件的觸發和監聽,提供了一種在組件之間通信的機制
在 Vue 中定義全局事件總線的一般步驟是:
- 創建一個新的 Vue 實例作為事件總線。
- 將這個 Vue 實例掛載到 Vue 的原型上,通常使用
$bus
作為屬性名。
示例如下:
// main.js 或其他入口文件
import Vue from 'vue';
import App from './App.vue'; // 創建一個新的 Vue 實例作為事件總線
const EventBus = new Vue(); // 將事件總線掛載到 Vue 的原型上
Vue.prototype.$bus = EventBus; new Vue({ render: h => h(App),
}).$mount('#app');
之后,在任何一個 Vue 組件中,你都可以通過 this.$bus
來訪問這個全局事件總線,然后使用 $emit
方法來觸發事件,或者使用 $on
、$off
、$once
方法來監聽、取消監聽或一次性監聽事件。
// 在某個組件中觸發事件
this.$bus.$emit('event-name', payload); // 在另一個組件中監聽事件
this.$bus.$on('event-name', (payload) => { // 處理事件邏輯
});
雖然全局事件總線提供了一種便捷的組件間通信方式,但它也有一些潛在的問題。過度使用全局事件總線可能導致組件之間的耦合度過高,使得代碼難以理解和維護。因此,在使用全局事件總線時,需要權衡其便利性和潛在的風險,并考慮其他組件間通信的方式,如 props、事件、Vuex 等
父通過ref、$refs向子拿值
? 用ref
給子組件打標記,使用$refs
獲取到的是vc組件(子組件)實例對象,拿到了vc實例對象了,我們就可以對子組件進行操作,拿取子組件中的值
son.vue
<template><div>我是子組件</div>
</template>
<script>
export default {name: 'mySon',data() {return {msg: '我是子組件中的數據'}}
}
</script>
App.vue
<template><div><son ref="son"></son></div>
</template>
<script>
import son from './components/son.vue';
export default {components: { son },mounted() {console.log(this.$refs.son.msg) // 我是子組件中的數據}
}
</script>
父子通信$children
/$parent
在 Vue 2 中,$children
和 $parent
是兩個實例屬性,它們允許你直接訪問組件的父級或子級組件實例。然而,這種直接的父子通信方式通常不推薦用于復雜的組件結構,因為它們使組件之間的依賴關系變得隱式和難以維護。但是,在某些簡單的場景下,它們可能是有用的。
$children
$children
是一個數組,包含當前組件實例的直接子組件。請注意,$children
并不保證順序,也不是響應式的。
示例
// 父組件
export default {mounted() {console.log(this.$children); // 輸出子組件的數組}
}// 子組件(可能有很多)
// ...
$parent
$parent
屬性用來訪問當前組件實例的父組件實例。
示例
// 子組件
export default {mounted() {console.log(this.$parent); // 輸出父組件的實例// 你可以通過 this.$parent 訪問父組件的數據、方法等}
}
注意事項
- 不推薦過度使用:過度依賴
$parent
和$children
會導致組件之間的耦合度過高,使得代碼難以維護和理解。在復雜的項目中,應該優先使用 props 和 events 進行父子通信,或者使用 Vuex 進行狀態管理。 - 非響應式:
$children
數組不是響應式的,也就是說,當子組件被添加或刪除時,Vue 不會觸發視圖更新。因此,你不應該試圖通過修改$children
來控制組件的渲染。 - 順序不保證:
$children
數組中的組件順序并不保證與它們在模板中的順序一致。因此,你不應該依賴$children
數組的順序來訪問特定的子組件。 - 循環引用:在某些情況下,可能會出現父子組件之間循環引用的問題。這通常是由于不恰當的組件設計或錯誤的父子關系導致的。在這種情況下,使用
$parent
和$children
可能會導致不可預測的結果。 - 替代方案:對于父子組件之間的通信,推薦使用 props 和 events。對于跨組件通信,可以考慮使用 Vuex 或 EventBus(盡管 EventBus 在大型項目中也可能導致問題)。
跨層級通信provide/inject
provide
和 inject
主要在開發高階插件/組件庫時使用。它們允許一個祖先組件向其所有子孫組件提供一個依賴,不論組件層次有多深,該依賴都可以注入進來。
// 祖先組件
export default { provide() { return { foo: 'foo' }; }, // ...
} // 子孫組件
export default { inject: ['foo'], created() { console.log(this.foo); // "foo" } // ...
}
注意:provide
和 inject
主要是為高階插件/組件庫開發而設計的。在大多數應用中,應優先使用 props 和 events 進行父子組件通信,并且對于跨多層級的通信,Vuex 會是更好的選擇
跨層級通信attrs/listeners
在 Vue 2 中,$attrs
和 $listeners
是兩個特殊的屬性,允許你在組件之間透傳(passthrough)屬性和事件監聽器。
-
$attrs
包含了父組件中未被子組件聲明的屬性(attribute)。換句話說,它是一個對象,包含了父組件傳遞給子組件的所有屬性,除了那些在子組件中已經通過props
選項明確聲明的。 -
$listeners
包含了父組件傳遞給子組件的所有事件監聽器(即v-on
綁定的事件)。
這兩個特性在你需要創建封裝組件或高階組件時非常有用,因為它們允許你將額外的屬性和事件監聽器傳遞給子組件,而無需在封裝組件中顯式聲明它們。
下面是一個簡單的例子來說明 $attrs
和 $listeners
的用法:
<!-- ParentComponent.vue -->
<template><div><ChildComponent :prop1="value1" @event1="handleEvent1" class="extra-class" /></div>
</template><script>
import ChildComponent from './ChildComponent.vue';export default {components: {ChildComponent},data() {return {value1: 'someValue'};},methods: {handleEvent1() {console.log('Event 1 triggered');}}
};
</script>
<!-- ChildComponent.vue -->
<template><div><GrandChildComponent v-bind="$attrs" v-on="$listeners" /></div>
</template><script>
import GrandChildComponent from './GrandChildComponent.vue';export default {components: {GrandChildComponent},props: ['prop1'] // 假設 ChildComponent 只聲明了一個 prop1
};
</script>
<!-- GrandChildComponent.vue -->
<template><div><!-- 這里可以訪問通過 $attrs 傳遞過來的 class 和其他未聲明的屬性 --><!-- 也可以觸發通過 $listeners 傳遞過來的事件 --></div>
</template><script>
export default {mounted() {// 假設我們要在這里觸發 event1 事件this.$emit('event1');}
};
</script>
在上面的例子中,ParentComponent
傳遞了一個 prop1
屬性,一個 event1
事件監聽器,以及一個未聲明的 class
屬性給 ChildComponent
。在 ChildComponent
中,我們只聲明了 prop1
作為 prop,因此 class
屬性會被包含在 $attrs
中。同時,event1
事件監聽器會被包含在 $listeners
中。
當我們使用 v-bind="$attrs"
和 v-on="$listeners"
將這些屬性和事件監聽器透傳給 GrandChildComponent
時,GrandChildComponent
可以接收到這些屬性和事件,就好像它們是直接被傳遞給它的一樣。這樣,GrandChildComponent
可以訪問 class
屬性和觸發 event1
事件,盡管這些屬性和事件是在 ParentComponent
中定義的。
vuex
Vuex 是一個插件,可以幫我們管理 Vue 通用的數據 (多組件共享的數據)