在Vue的項目開發裏面,組件之間的通訊實在是太常見了,雖然說Vue已經出了好久了,但我接觸它的時間仍不是不少,趨於業務開發,有時候會踩到一些坑,也是初級開發者很容易遇到的問題,固然網上也都有不少解決方案的文章,不過每次一遇到問題就百度一下的習慣並無讓本身系統的理解Vue裏面的通訊機制。好比.sync這個修飾符,使用ElementUI的Dialog組件常見到,但一直是拿來即用,沒有思索過具體用處,遂總結整理一下在Vue中組件之間的通訊機制,方便往後開發中按照場景使用更合適的API,而不是隻會$emit
和$on
這一種用法。html
本篇文章主要參考學習了掘金的一篇文章,指路➡️ vue組件通訊全揭祕 , 感受講的很詳細,不少地方給我一種恍然大悟的感受,很是感謝。vue
在vue中,咱們最多見的恐怕莫過於data了,vue經過{{}}綁定動態數據,當data的值發生變化時,能夠同步的更新到視圖中。vuex
而props經常使用於數據傳遞,好比子組件想要使用父組件中的數據,就能夠經過props接收父組件的值。而父組件使用v-bind來把數據傳遞給子組件。當父組件中的數據發生改變時,子組件中對應的視圖層也會相應的更新。數組
以上二者裏的每一個數據都是行爲操做須要的數據或者模板 view 須要渲染的數據,一旦其中一個屬性發生變化,則全部關聯的行爲操做和數據渲染的模板上的數據同一時間進行同步變化。瀏覽器
// 父組件A
<template>
<div>
<p>test: {{test}}</p>
<p>msgData: {{test}}</p>
<ComponentB :msg='msgData'></ComponentB>
</div>
</template>
<script>
import ComponentB from './ComponentB.vue'
export default {
data () {
return {
test: '我是測試數據',
msgData: '我是傳遞給子組件的數據'
}
},
components : {
ComponentB
}
}
</script>
// 子組件B
<template>
<div>
<p>msgData: {{msgData}}</p>
</div>
</template>
<script>
export default {
name: 'ComponentB',
props: [ 'msgData'],
}
</script>
複製代碼
雖然都會影響視圖層的數據,不過props跟data的使用仍是有些區別的。下面就來講說我在代碼中遇到過的有關props的坑(也多是常見的錯誤):bash
Props踩過的坑數據結構
在子組件中不能夠直接修改props中的數據,這也正是vue中單向數據流的特性,父組件向下傳遞的數據,若是父組件發生改變,會同步的反應在子組件中;但子組件的數據卻不會同步影響上一層,就像水同樣,只能往下走。(固然能夠經過其餘方式來影響父組件的數據,好比事件)dom
雖然說props的數據是不能顯式的直接改變,但咱們能夠經過間接的方式來修改數據。函數
props: ['msg'],
data() {
return {
myMsg: this.msg
}
}
複製代碼
props:['msg']
computed : {
myMsg () {
return this.msg;
}
}
複製代碼
不過由於對象和數組是引用類型,指向同一個內存空間,因此不要經過computed來對父組件傳遞來的引用類型數據進行計算過濾,改變數據會影響到父組件的狀態。post
props對象傳遞簡寫
<!--父組件:-->
<!--html部分: -->
<demo v-bind='msg'></demo>
<!--js部分:-->
data () {
return {
msg : {a:1,b:2}
}
}
<!--子組件:-->
props: ['a','b']
<!--父組件傳遞的過程至關於以下:-->
<demo :a='a' :b='b' ></demo>
複製代碼
$emit
、$on
和 v-on
上面的props中說過,子組件不能直接改變父組件傳遞進來的數據,除了經過data或computed轉存的間接方式來改變傳遞的數據外,還有另外一種方式,就是$emit
事件。子組件用通訊的方式來告知父組件進行數據的更新。
$on(eventName, callback)
監聽事件, 第二個參數是回調函數,回調函數的參數爲$emit傳遞的數據內容$emit(eventName, [...arg])
觸發事件, 第二個參數會傳遞給on監聽器的回調函數固然,v-model也是用於雙向綁定的,不過由於太常見了,暫時沒有作進一步的分析。
<comp :foo.sync="bar"></comp>
<comp :foo="bar" @update:foo="val => bar = val"></comp>
this.$emit('update:foo', newValue)
<!--常見的如餓了麼組件中的:current-page.sync-->
<el-pagination
layout="prev, pager, next"
:total="meta.total"
@current-change="load"
:current-page.sync="meta.current_page"
background>
</el-pagination>
<!--js部分:-->
props: {
meta: {
type: Object,
required: true,
},
},
複製代碼
再如,餓了麼組件Dialog:
<el-dialog
title="提示"
:visible.sync="dialogVisible"
width="30%"
:before-close="handleClose">
<span>這是一段信息</span>
<span slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false">取 消</el-button>
<el-button type="primary" @click="dialogVisible = false">確 定</el-button>
</span>
</el-dialog>
複製代碼
$attrs,$listeners
, 深層次數據傳遞(Vue v2.4及以上使用)當數據層次變多後,已經不只包括父子組件這麼簡單的關係了,可能會有第三層、第四層組件,複雜的狀況下能夠用vuex,但簡單狀況下只是偶爾有多層組件關係時,能夠選擇使用$attrs,這種方式 組件封裝用的較多
先來解釋一下這兩個屬性:
$attrs
: 包含了父做用域中不做爲 props 被識別 (且獲取) 的特性綁定 ( class 和 style 除外)。當一個組件沒有聲明任何 props 時,這裏會包含全部父做用域的綁定 ( class 和 style 除外),而且能夠經過 v-bind="$attrs"
傳入內部組件 —— 在建立高級別的組件時很是有用。$listeners
: 包含了父做用域中的 (不含 .native 修飾器的) v-on 事件監聽器。它能夠經過 v-on="$listeners" 傳入內部組件 —— 在建立更高層次的組件時很是有用事實上,你能夠把 $attrs
和 $listeners
比做兩個集合,其中 $attrs
是一個屬性集合,而 $listeners
是一個事件集合,二者都是 以對象的形式來保存數據 。
好比咱們有三個組件A,B,C,分別是父組件、子組件、孫組件,若是想從A組件傳遞數據msg給C組件:
常規作法是一層一層使用props來傳遞,但實際上B並不須要A傳遞的這個msg,除了這種思路外,還有一種寫法,
A組件:
<template>
<div class="home">
這裏是首頁
<ChildB
:one="one"
:two="two"
@clickOne.native="triggerOne"
@clickTwo="triggerTwo"
></ChildB>
</div>
</template>
<script>
import ChildB from '../components/ChildB.vue';
export default {
name: 'home',
components: {
ChildB,
},
data() {
return {
one: 'data 1',
two: 'data 2',
};
},
methods: {
triggerOne() {
console.log('triggerOne');
},
triggerTwo() {
console.log('triggerTwo');
},
},
};
</script>
複製代碼
B組件:
<template>
<div>
B 組件
{{one}}:
<ChildC></ChildC>
</div>
</template>
<script>
import ChildC from './ChildC.vue';
export default {
components: {
ChildC,
},
props: ['one'],
created() {
console.log('$attrsB:', this.$attrs);
console.log('$listenersB:', this.$listeners);
},
};
</script>
複製代碼
C組件:
<template>
<div>
我是 C 組件
</div>
</template>
<script>
export default {
created() {
console.log('$attrsC:', this.$attrs);
console.log('$listenersC:', this.$listeners);
},
};
</script>
複製代碼
此時控制檯打印出:
$attrsB: {two: "data 2"}
$listenersB: {clickTwo: ƒ invoker()}
$attrsC:{}
$listenersC: {}
複製代碼
這時候B組件能夠經過this.$attrs
,this.$listeners
,直接訪問A組件的屬性和事件,注意,這個屬性是非props中的才能經過這種方式訪問,而帶.native修飾器(監聽組件根元素的原生事件)的事件也一樣訪問不到。
注意,此時C組件使用this.$attrs
,this.$listeners
仍是訪問不到A組件的屬性和事件的。
咱們能夠經過在B組件中調用C組件的過程當中使用v-on="$listeners"
一級級地往下傳遞,此時C組件中就能夠訪問到A組件的事件了
<!--B組件:-->
<ChildC v-on="$listeners"></ChildC>
<!--C組件-->
<template>
<div>
我是 C 組件
</div>
</template>
<script>
export default {
created() {
console.log('$attrsC:', this.$attrs);
console.log('$listenersC:', this.$listeners);
this.$listeners.clickTwo();
},
};
</script>
複製代碼
輸出:
$attrsC: {}
$listenersC: {clickTwo: ƒ}
triggerTwo
複製代碼
還記得咱們上面的two屬性嘛?咱們給B組件傳遞的props屬性只有one沒有two,那這個時候two屬性去哪裏了呢?
<div two="data 2">
A組件到C組件的數據傳遞 上面簡單介紹了下$attrs
,和$listeners
的基礎用法,回到三個組件傳遞數據這一點上,咱們看一下多層級組件到底怎麼藉助$attrs
,和$listeners
來實現數據通訊的。
A組件不變,
B組件:
<template>
<div>
B 組件
<p>props: {{one}} </p>
<p>$attrs: {{$attrs}} </p>
<p>$listeners: {{$listeners}} </p>
<ChildC v-on="$listeners" v-bind="$attrs"></ChildC>
</div>
</template>
<script>
import ChildC from './ChildC.vue';
export default {
components: {
ChildC,
},
inheritAttrs: false,
props: ['one'],
created() {
console.log('ComponentB', this.$attrs, this.$listeners);
},
};
</script>
複製代碼
C組件:
<template>
<div>
我是 C 組件
<p>props: {{two}} </p>
<p>$attrs: {{$attrs}} </p>
<p>$listeners: {{$listeners}} </p>
</div>
</template>
<script>
export default {
props: ['two'],
created() {
console.log('ComponentC', this.$attrs, this.$listeners);
},
};
</script>
複製代碼
此時控制檯上打印出:
ComponentB {two: "data 2"} {clickTwo: ƒ}
ComponentC {} {clickTwo: ƒ}
複製代碼
能夠發現,C組件能夠直接經過props來繼承A組件中的two屬性,不過這是在B組件直接往下傳遞v-bind="$attrs"
的前提下。
代碼裏有一段 inheritAttrs: false,
這就是爲了禁止非props的屬性添加到DOM元素上,如今打開瀏覽器的調試窗口,就不會看見two屬性了
$children
/ $parent
方式這種方式適用於木偶組件,何爲木偶組件?就是爲了業務頁面進行拆分而造成的組件模式。好比一個頁面,能夠分多個模塊,而每個模塊與其他頁面並無公用性,只是純粹拆分。
這種狀況下的組件明確的知道本身的父組件是哪個,且不須要複用,就能夠經過 $children
/ $parent
方式來進行通訊了。
$parent
好比A組件種有one和two兩個屬性,咱們不須要經過v-bind來傳遞,就能夠直接在A的子組件中經過this.$parent.one
和this.$parent.two
來獲取。而且還能夠改變父組件的數據:this.$parent.one = '父組件one被改了'
,
一樣事件也能夠經過這種方式調用,父組件定義parentMethods()方法,
子組件調用:
this.$parent.parentMethods()
複製代碼
$children
$children
和 $parent
相反,是父組件拿到子組件的實例,須要注意的是,$children
是以一個數組的形式包裹。
this.$children.forEach(item => {
console.log(item);
})
複製代碼
ref:若是在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;若是用在子組件上,引用就指向組件實例,能夠經過實例直接調用組件的方法或訪問數據。獲取 Dom 元素就是爲了進行一些 Dom 操做,須要注意的一點就是,要在 Dom 加載完成後使用,不然可能獲取不到。
使用場景:
ref的三種使用方式:
一、ref 加在普通的元素上,用this.ref.name
獲取到的是dom元素
<input type="text" ref="input1"/>
<!--JS部分改變數據-->
this.$refs.input1.value ="test"; //this.$refs.input1 減小獲取dom節點的消耗
複製代碼
二、ref 加在子組件上,用this.ref.name
獲取到的是組件實例,可使用組件的全部方法。
<!--父組件:-->
<child ref="childComponent"></child>
<!--JS調用:-->
this.$refs.childComponent.getLocalData()
<!--子組件定義方法:-->
getLocalData(){
// ...
}
複製代碼
三、如何利用 v-for 和 ref 獲取一組數組或者dom 節點
<ul v-for="(item, key) in list" :key="key">
<li ref="item">姓名:{{item.name}}, 年齡: {{item.age}}</li>
</ul>
<!--JS部分 -->
data() {
return {
list: [
{ name: 'armor', age: 24 },
{ name: 'abtion', age: 23 },
{ name: 'lili', age: 12 },
{ name: 'yangyang', age: 29 },
],
}
},
created() {
console.log('refs: ', this.$refs);
}
複製代碼
這裏打印的結果是:
refs: {
item: [
0: li,
1: li,
2: li,
3: li
]
}
複製代碼
每個li就是對應的DOM元素,而後就能夠對其進行操做了。
如First組件與同級組件Second相互通訊,這兩個沒有共同父組件。
import Vue from 'vue'
export default new Vue()
複製代碼
import Bus from './bus.js'
<!--First組件觸發:-->
Bus.$emit('fromFirst', '來自A的組件')
<!--B組件監聽-->
Bus.$on('fromFirst', ( Amsg )=> {
this.Bmsg = Amsg
console.log('同級組件交互成功')
})
複製代碼
其實Vue的通訊事件不止上面幾種,還有vuex中的數據交互,可是vuex比較複雜,通常不是很複雜的業務其實用不上vuex,用上面幾種方式實現組件之間的通訊足矣。
文章參考: