前言
vue是數據驅動視圖更新的框架, 我們平時開發,都會把頁面不同模塊拆分成一個一個vue組件, 所以對於vue來說組件間的數據通信非常重要,那麼組件之間如何進行數據通信的呢?
首先我們需要知道在vue中組件之間存在什麼樣的關係, 才更容易理解他們的通信方式。
一般我們分爲如下關係:
父子組件之間通信
非父子組件之間通信(兄弟組件、隔代關係組件、跨層級組件等)
Vue2.x 組件通信共有12種
- props
- $emit / v-on
- .sync
- v-model
- ref
- $children / $parent
- $attrs / $listeners
- provide / inject
- EventBus
- Vuex
- $root
- slot
- 路由傳參
- observable
父子組件通信可以用:
- props
- $emit / v-on
- $attrs / $listeners
- ref
- .sync
- v-model
- $children / $parent
兄弟組件通信可以用:
- EventBus
- Vuex
- $parent
跨層級組件通信可以用:
- provide/inject
- EventBus
- Vuex
- $attrs / $listeners
- $root
Vue2.x 組件通信使用寫法
下面把每一種組件通信方式的寫法一一列出
1. props
父組件向子組件傳送數據,這應該是最常用的方式了
子組件接收到數據之後,不能直接修改父組件的數據。會報錯,所以當父組件重新渲染時,數據會被覆蓋。如果子組件內要修改的話推薦使用 computed
格式:
// 數組:不建議使用
props:[]
// 對象
props:{
inpVal:{
type:Number, //傳入值限定類型
// type 值可爲String,Number,Boolean,Array,Object,Date,Function,Symbol
// type 還可以是一個自定義的構造函數,並且通過 instanceof 來進行檢查確認
required: true, //是否必傳
default:200, //默認值,對象或數組默認值必須從一個工廠函數獲取如 default:()=>[]
validator:(value) {
// 這個值必須匹配下列字符串中的一個
return ['success', 'warning', 'danger'].indexOf(value) !== -1
}
}
}
事例:
// Parent.vue 傳送
<template>
<child :msg="msg"></child>
</template>
// Child.vue 接收
export default {
// 寫法一 用數組接收
props:['msg'],
// 寫法二 用對象接收,可以限定接收的數據類型、設置默認值、驗證等
props:{
msg:{
type:String,
default:'這是默認數據'
}
},
mounted(){
console.log(this.msg)
},
}
注意:
prop 只可以從上一級組件傳遞到下一級組件(父子組件),即所謂的單向數據流。而且 prop 只讀,不可被修改,所有修改都會失效並警告。
- 第一,不應該在一個子組件內部改變 prop,這樣會破壞單向的數據綁定,導致數據流難以理解。如果有這樣的需要,可以通過 data 屬性接收或使用 computed 屬性進行轉換。
- 第二,如果 props 傳遞的是引用類型(對象或者數組),在子組件中改變這個對象或數組,父組件的狀態會也會做相應的更新,利用這一點就能夠實現父子組件數據的“雙向綁定”,雖然這樣實現能夠節省代碼,但會犧牲數據流向的簡潔性,令人難以理解,最好不要這樣去做。
- 想要實現父子組件的數據“雙向綁定”,可以使用 v-model 或 .sync。
2. $emit / v-on
子傳父的方法,子組件通過派發事件的方式給父組件數據,或者觸發父組件更新等操作
// Child.vue 派發
export default {
data(){
return { msg: "這是發給父組件的信息" }
},
methods: {
handleClick(){
this.$emit("sendMsg",this.msg)
}
},
}
// Parent.vue 響應
<template>
<child v-on:sendMsg="getChildMsg"></child>
// 或 簡寫
<child @sendMsg="getChildMsg"></child>
</template>
export default {
methods:{
getChildMsg(msg){
console.log(msg) // 這是父組件接收到的消息
}
}
}
3. v-model
和 .sync 類似,可以實現將父組件傳給子組件的數據爲雙向綁定,子組件通過 $emit 修改父組件的數據
// Parent.vue
<template>
<child v-model="value"></child>
</template>
<script>
export default {
data(){
return {
value:1
}
}
}
// Child.vue
<template>
<input :value="value" @input="handlerChange">
</template>
export default {
props:["value"],
// 可以修改事件名,默認爲 input
model:{
// prop:'value', // 上面傳的是value這裏可以不寫,如果屬性名不是value就要寫
event:"updateValue"
},
methods:{
handlerChange(e){
this.$emit("input", e.target.value)
// 如果有上面的重命名就是這樣
this.$emit("updateValue", e.target.value)
}
}
}
</script>
4. ref
ref 如果在普通的DOM元素上,引用指向的就是該DOM元素;
如果在子組件上,引用的指向就是子組件實例,然後父組件就可以通過 ref 主動獲取子組件的屬性或者調用子組件的方法
// Child.vue
export default {
data(){
return {
name:"RDIF"
}
},
methods:{
someMethod(msg){
console.log(msg)
}
}
}
// Parent.vue
<template>
<child ref="child"></child>
</template>
<script>
export default {
mounted(){
const child = this.$refs.child
console.log(child.name) // RDIF
child.someMethod("調用了子組件的方法")
}
}
</script>
5. .sync
可以幫我們實現父組件向子組件傳遞的數據 的雙向綁定,所以子組件接收到數據後可以直接修改,並且會同時修改父組件的數據
// Parent.vue
<template>
<child :page.sync="page"></child>
</template>
<script>
export default {
data(){
return {
page:1
}
}
}
// Child.vue
export default {
props:["page"],
computed(){
// 當我們在子組件裏修改 currentPage 時,父組件的 page 也會隨之改變
currentPage {
get(){
return this.page
},
set(newVal){
this.$emit("update:page", newVal)
}
}
}
}
</script>
6. $attrs / $listeners
多層嵌套組件傳遞數據時,如果只是傳遞數據,而不做中間處理的話就可以用這個,比如父組件向孫子組件傳遞數據時。
$attrs
:包含父作用域裏除 class 和 style 除外的非 props 屬性集合。通過 this.$attrs 獲取父作用域中所有符合條件的屬性集合,然後還要繼續傳給子組件內部的其他組件,就可以通過 v-bind="$attrs"。
場景:如果父傳子有很多值,那麼在子組件需要定義多個 props
解決:$attrs獲取子傳父中未在 props 定義的值
// 父組件
<home title="這是標題" width="80" height="80" imgUrl="imgUrl"/>
// 子組件
mounted() {
console.log(this.$attrs) //{title: "這是標題", width: "80", height: "80", imgUrl: "imgUrl"}
},
相對應的如果子組件定義了 props,打印的值就是剔除定義的屬性。
props: {
width: {
type: String,
default: ''
}
},
mounted() {
console.log(this.$attrs) //{title: "這是標題", height: "80", imgUrl: "imgUrl"}
},
$listeners
:包含父作用域裏 .native 除外的監聽事件集合。如果還要繼續傳給子組件內部的其他組件,就可以通過 v-on="$linteners"。
場景:子組件需要調用父組件的方法
解決:父組件的方法可以通過 v-on="$listeners" 傳入內部組件——在創建更高層次的組件時非常有用
// 父組件
<home @change="change"/>
// 子組件
mounted() {
console.log(this.$listeners) //即可拿到 change 事件
}
如果是孫組件要訪問父組件的屬性和調用方法,直接一級一級傳下去就可以。
7. $children / $parent
$children
:獲取到一個包含所有子組件(不包含孫子組件)的 VueComponent 對象數組,可以直接拿到子組件中所有數據和方法等。
$parent
:獲取到一個父節點的 VueComponent 對象,同樣包含父節點中所有數據和方法等
// Parent.vue
export default{
mounted(){
this.$children[0].someMethod() // 調用第一個子組件的方法
this.$children[0].name // 獲取第一個子組件中的屬性
}
}
// Child.vue
export default{
mounted(){
this.$parent.someMethod() // 調用父組件的方法
this.$parent.name // 獲取父組件中的屬性
}
}
$children
和$parent
並不保證順序,也不是響應式的,只能拿到一級父組件和子組件。
8. provide / inject
provide / inject 爲依賴注入,主要爲高階插件/組件庫提供用例。說是不推薦直接用於應用程序代碼中,但是在一些插件或組件庫裏卻是被常用,所以我覺得用也沒啥,還挺好用的。
provide
:可以讓我們指定想要提供給後代組件的數據或方法
inject
:在任何後代組件中接收想要添加在這個組件上的數據或方法,不管組件嵌套多深都可以直接拿來用
要注意的是 provide 和 inject 傳遞的數據不是響應式的,也就是說用 inject 接收來數據後,provide 裏的數據改變了,後代組件中的數據不會改變,除非傳入的就是一個可監聽的對象
所以建議還是傳遞一些常量或者方法
// 父組件
export default{
// 方法一 不能獲取 this.xxx,只能傳寫死的
provide:{
name:"RDIF",
},
// 方法二 可以獲取 this.xxx
provide(){
return {
name:"RDIF",
msg: this.msg // data 中的屬性
someMethod:this.someMethod // methods 中的方法
}
},
methods:{
someMethod(){
console.log("這是注入的方法")
}
}
}
// 後代組件
export default{
inject:["name","msg","someMethod"],
mounted(){
console.log(this.msg) // 這裏拿到的屬性不是響應式的,如果需要拿到最新的,可以在下面的方法中返回
this.someMethod()
}
}
9. EventBus
EventBus 是中央事件總線,不管是父子組件,兄弟組件,跨層級組件等都可以使用它完成通信操作。
- 聲明一個全局Vue實例變量 EventBus , 把所有的通信數據,事件監聽都存儲到這個變量上;
- 類似於 Vuex。但這種方式只適用於極小的項目;
- 原理就是利用emit 並實例化一個全局 vue 實現數據共享;
- 可以實現平級,嵌套組件傳值,但是對應的事件名eventTarget必須是全局唯一的;
定義方式有三種:
// 方法一
// 抽離成一個單獨的 js 文件 Bus.js ,然後在需要的地方引入
// Bus.js
import Vue from "vue"
export default new Vue()
// 方法二 直接掛載到全局
// main.js
import Vue from "vue"
Vue.prototype.$bus = new Vue()
// 方法三 注入到 Vue 根對象上
// main.js
import Vue from "vue"
new Vue({
el:"#app",
data:{
Bus: new Vue()
}
})
使用如下,以方法一按需引入爲例:
// 在需要向外部發送自定義事件的組件內
<template>
<button @click="handlerClick">按鈕</button>
</template>
import Bus from "./Bus.js"
export default{
methods:{
handlerClick(){
// 自定義事件名 sendMsg
Bus.$emit("sendMsg", "這是要向外部發送的數據")
}
}
}
// 在需要接收外部事件的組件內
import Bus from "./Bus.js"
export default{
mounted(){
// 監聽事件的觸發
Bus.$on("sendMsg", data => {
console.log("這是接收到的數據:", data)
})
},
beforeDestroy(){
// 取消監聽
Bus.$off("sendMsg")
}
}
以方法二直接掛載在全局:
// 在 main.js
Vue.prototype.$eventBus=new Vue()
// 傳值組件
this.$eventBus.$emit('eventTarget','這是eventTarget傳過來的值')
// 接收組件
this.$eventBus.$on("eventTarget",v=>{
console.log('eventTarget',v);//這是eventTarget傳過來的值
})
10. Vuex
- Vuex 是狀態管理器,集中式存儲管理所有組件的狀態。
- 適合數據共享多的項目裏面,因爲如果只是簡單的通訊,使用起來會比較重。
state:定義存貯數據的倉庫 ,可通過this.$store.state 或mapState訪問。
getter:獲取 store 值,可認爲是 store 的計算屬性,可通過this.$store.getter 或 mapGetters訪問。
mutation:同步改變 store 值,爲什麼會設計成同步,因爲mutation是直接改變 store 值,vue 對操作進行了記錄,如果是異步無法追蹤改變,可通過mapMutations調用。
action:異步調用函數執行mutation,進而改變 store 值,可通過 this.$dispatch或mapActions訪問。
modules:模塊,如果狀態過多,可以拆分成模塊,最後在入口通過...解構引入。
這一塊內容過長,如果基礎不熟的話可以看這個Vuex,然後大致用法如下:
比如創建這樣的文件結構
index.js 裏內容如下
import Vue from 'vue'
import Vuex from 'vuex'
import getters from './getters'
import actions from './actions'
import mutations from './mutations'
import state from './state'
import user from './modules/user'
Vue.use(Vuex)
const store = new Vuex.Store({
modules: {
user
},
getters,
actions,
mutations,
state
})
export default store
然後在 main.js 引入
import Vue from "vue"
import store from "./store"
new Vue({
el:"#app",
store,
render: h => h(App)
})
然後在需要的使用組件裏
import { mapGetters, mapMutations } from "vuex"
export default{
computed:{
// 方式一 然後通過 this.屬性名就可以用了
...mapGetters(["引入getters.js裏屬性1","屬性2"])
// 方式二
...mapGetters("user", ["user模塊裏的屬性1","屬性2"])
},
methods:{
// 方式一 然後通過 this.屬性名就可以用了
...mapMutations(["引入mutations.js裏的方法1","方法2"])
// 方式二
...mapMutations("user",["引入user模塊裏的方法1","方法2"])
}
}
// 或者也可以這樣獲取
this.$store.state.xxx
this.$store.state.user.xxx
11. $root
$root
可以拿到 App.vue 裏的數據和方法
// 父組件
mounted(){
console.log(this.$root) //獲取根實例,最後所有組件都是掛載到根實例上
console.log(this.$root.$children[0]) //獲取根實例的一級子組件
console.log(this.$root.$children[0].$children[0]) //獲取根實例的二級子組件
}
12. slot
將父組件的 template 傳入子組件
分類:
A.匿名插槽(也叫默認插槽): 沒有命名,有且只有一個;
// 父組件
<todo-list>
<template v-slot:default>
任意內容
<p>我是匿名插槽 </p>
</template>
</todo-list>
// 子組件
<slot>我是默認值</slot>
//v-slot:default寫上感覺和具名寫法比較統一,容易理解,也可以不用寫
B.具名插槽: 相對匿名插槽組件slot標籤帶name命名的;
// 父組件
<todo-list>
<template v-slot:todo>
任意內容
<p>我是匿名插槽 </p>
</template>
</todo-list>
//子組件
<slot name="todo">我是默認值</slot>
C.作用域插槽: 子組件內數據可以被父頁面拿到(解決了數據只能從父頁面傳遞給子組件)
// 父組件
<todo-list>
<template v-slot:todo="slotProps" >
{{slotProps.user.firstName}}
</template>
</todo-list>
//slotProps 可以隨意命名
//slotProps 接取的是子組件標籤slot上屬性數據的集合所有v-bind:user="user"
// 子組件
<slot name="todo" :user="user" :test="test">
{{ user.lastName }}
</slot>
data() {
return {
user:{
lastName:"Zhang",
firstName:"yue"
},
test:[1,2,3,4]
}
},
// {{ user.lastName }}是默認數據 v-slot:todo 當父頁面沒有(="slotProps")
13、路由傳參
1.方案一
// 路由定義
{
path: '/describe/:id',
name: 'Describe',
component: Describe
}
// 頁面傳參
this.$router.push({
path: `/describe/${id}`,
})
// 頁面獲取
this.$route.params.id
2.方案二
// 路由定義
{
path: '/describe',
name: 'Describe',
component: Describe
}
// 頁面傳參
this.$router.push({
name: 'Describe',
params: {
id: id
}
})
// 頁面獲取
this.$route.params.id
3.方案三
// 路由定義
{
path: '/describe',
name: 'Describe',
component: Describe
}
// 頁面傳參
this.$router.push({
path: '/describe',
query: {
id: id
`}
)
// 頁面獲取
this.$route.query.id
4.三種方案對比
方案二參數不會拼接在路由後面,頁面刷新參數會丟失
方案一和三參數拼接在後面,醜,而且暴露了信息
14、Vue.observable
用法:讓一個對象可響應。Vue 內部會用它來處理 data 函數返回的對象;
返回的對象可以直接用於渲染函數和計算屬性內,並且會在發生改變時觸發相應的更新;
也可以作爲最小化的跨組件狀態存儲器,用於簡單的場景。
通訊原理實質上是利用Vue.observable實現一個簡易的 vuex
// 文件路徑 - /store/store.js
import Vue from 'vue'
export const store = Vue.observable({ count: 0 })
export const mutations = {
setCount (count) {
store.count = count
}
}
//使用
<template>
<div>
<label for="bookNum">數 量</label>
<button @click="setCount(count+1)">+</button>
<span>{{count}}</span>
<button @click="setCount(count-1)">-</button>
</div>
</template>
<script>
import { store, mutations } from '../store/store' // Vue2.6新增API Observable
export default {
name: 'Add',
computed: {
count () {
return store.count
}
},
methods: {
setCount: mutations.setCount
}
}
</script>
參考資料
vue.js: https://v2.cn.vuejs.org/
vuex是什麼:https://v3.vuex.vuejs.org/zh/
工作中要使用Git,看這篇文章就夠了:http://www.guosisoft.com/article/detail/410508049313861
企業數字化轉型如何做?看過來:http://www.guosisoft.com/article/detail/408745545576517
結語
如果本文對你有一點點幫助,點個贊支持一下吧,你的每一個【贊】都是我創作的最大動力 _
更多技術文章請往:http://www.guosisoft.com/article,大家一起共同交流和進步呀