????眾所周知屿良,組件是 vue.js最強(qiáng)大的功能之一溶浴,而組件實(shí)例的作用域是相互獨(dú)立的,這就意味著不同組件之間的數(shù)據(jù)無(wú)法相互引用管引。那么組件之間如何通信呢?主要有闯两,
props
褥伴、$emit/$on
、vuex
漾狼、$parent / $children
重慢、$attrs/$listeners
和provide/inject
,本文將分別介紹組件間的通信方式逊躁。
上圖表示了組件之間所有可能的關(guān)系
A 和 B似踱、B 和 C、B 和 D 都是父子關(guān)系稽煤,C 和 D 是兄弟關(guān)系核芽,A 和 C 是隔代關(guān)系(可能隔多代)
第一種 props/$emit
,適用于父子組件之間的通信
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Vue組件間的通信</title>
</head>
<body>
<div id="app">
<h3>組件間的通信(父子組件之間的通信props和$emit)</h3>
<parent></parent>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
Vue.component('parent', {
template: `
<div>
<h2>父組件</h2>
<p>子組件傳來(lái)的數(shù)據(jù)==>{{ fromChildMsg }}</p>
<child :msgParent="msgParent" @sendMsgToParent="childMsgHandle"></child>
</div>
`,
data(){
return {
msgParent:'父組件的數(shù)據(jù)',
fromChildMsg:''
}
},
methods: {
childMsgHandle(msg){
this.fromChildMsg = msg
}
}
})
Vue.component('child', {
template: `
<div>
<h3>子組件</h3>
<p>父組件傳來(lái)的數(shù)據(jù)==>{{ msgParent }}</p>
</div>
`,
props:{
msgParent:{
type:String
}
},
data() {
return {
msgChild: '子組件的數(shù)據(jù)'
}
},
mounted () {
this.$emit('sendMsgToParent',this.msgChild)
}
})
const app = new Vue({
el: '#app',
data: {}
})
</script>
</html>
注意:在組件間通行過(guò)程中酵熙,Vue組件遵循單向數(shù)據(jù)流原則:就是數(shù)據(jù)只能通過(guò) props 由父組件流向子組件轧简,而子組件并不能通過(guò)修改 props 傳過(guò)來(lái)的數(shù)據(jù)修改父組件的相應(yīng)狀態(tài)。原因是:所有的 prop 都使得其父子 prop 之間形成了一個(gè)單向下行綁定:父級(jí) prop 的更新會(huì)向下流動(dòng)到子組件中匾二,但是反過(guò)來(lái)則不行哮独。這樣會(huì)防止從子組件意外改變父級(jí)組件的狀態(tài),從而導(dǎo)致你的應(yīng)用的數(shù)據(jù)流向難以理解察藐。額外的皮璧,每次父級(jí)組件發(fā)生更新時(shí),子組件中所有的 prop 都將會(huì)刷新為最新的值分飞。這意味著你不應(yīng)該在一個(gè)子組件內(nèi)部改變 prop悴务。如果你這樣做了,Vue 會(huì)在瀏覽器的控制臺(tái)中發(fā)出警告浸须。
運(yùn)行結(jié)果
第二種惨寿,$attrs
和 $listeners
,適用于后代組件的通信
-
$attrs
:包含了父作用域中不被prop
所識(shí)別 (且獲取) 的特性綁定 (class
和style
除外)删窒。當(dāng)一個(gè)組件沒(méi)有聲明任何prop
時(shí)裂垦,這里會(huì)包含所有父作用域的綁定屬性 (class
和style
除外),并且可以通過(guò)v-bind="$attrs"
傳入內(nèi)部組件肌索。 -
$listeners
:包含了父作用域中的 (不含.native
修飾器的)v-on
事件監(jiān)聽(tīng)器蕉拢。它可以通過(guò)v-on="$listeners"
傳入內(nèi)部組件。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Vue組件間的通信</title>
</head>
<body>
<div id="app">
<h3>組件間的通信(父組件和后代之間的通信$attrs和$listeners)</h3>
<parent />
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
Vue.component('parent', {
template: `
<div>
<h2>父組件</h2>
<p>子組件傳來(lái)的數(shù)據(jù)==>{{ fromChildMsg }}</p>
<p>后代件傳來(lái)的數(shù)據(jù)==>{{ fromProgenyMsg }}</p>
<child :msgParentToProgeny="msgParentToProgeny" :msgParent="msgParent" @sendMsgToParent="childMsgHandle" @progenyToParent="progenyMsgHandle"/>
</div>
`,
data(){
return {
msgParent:'父組件的數(shù)據(jù)',
msgParentToProgeny:'父組件數(shù)據(jù)給后代組件',
fromChildMsg:'',
fromProgenyMsg:''
}
},
methods: {
childMsgHandle(msg){
this.fromChildMsg = msg
},
progenyMsgHandle(msg){
this.fromProgenyMsg = msg
}
}
})
Vue.component('child', {
template: `
<div>
<h3>子組件</h3>
<p>父組件傳來(lái)的數(shù)據(jù)==>{{ msgParent }}</p>
// 后代組件中能直接觸發(fā) progenyToParent 的原因在于:子組件調(diào)用 后代組件時(shí),使用 v-on 綁定了 $listeners 屬性
// 通過(guò)v-bind 綁定 $attrs 屬性晕换,后代組件可以直接獲取到 父組件組件中傳遞下來(lái)的 props(除了 子組件中 props聲明的)
<progeny v-bind="$attrs" v-on="$listeners" />
</div>
`,
props:{
msgParent:{
type:String
}
},
data() {
return {
msgChild: '子組件的數(shù)據(jù)'
}
},
mounted () {
this.$emit('sendMsgToParent',this.msgChild)
}
})
Vue.component('progeny',{
template:`
<div>
<h3>后代組件</h3>
</div>
`,
data(){
return {
progenyMsg:'后代組件的信息'
}
},
mounted () {
this.$emit('progenyToParent',this.progenyMsg)
}
})
const app = new Vue({
el: '#app',
data: {}
})
</script>
</html>
運(yùn)行結(jié)果
第三種:事件總線方式午乓,在項(xiàng)目規(guī)模不大的情況下,完全可以使用中央事件總線 的方式,中央事件總線 EventBus 非常簡(jiǎn)單闸准,就是任意組件和組件之間打交道益愈,沒(méi)有多余的業(yè)務(wù)邏輯,只需要在狀態(tài)變化組件觸發(fā)一個(gè)事件夷家,然后在處理邏輯組件監(jiān)聽(tīng)該事件就可以蒸其。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Vue組件間的通信</title>
</head>
<body>
<div id="app"></div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
Vue.component('child1', {
template: `
<div>
<h3>子組件1</h3>
<button @click="$EventBus.$emit('child1Event',child1Msg)">bus</button>
</div>
`,
data() {
return {
child1Msg:'組件1的信息'
}
}
})
Vue.component('child2', {
template: `
<div>
<h3>子組件2</h3>
</div>
`,
mounted () {
this.$EventBus.$on('child1Event',msg=>{
console.log(msg)
})
}
})
// 定義事件總線
const EventBus = new Vue()
Vue.prototype.$EventBus = EventBus
const app = new Vue({
el: '#app',
template: `
<div>
<h3>組件間的通信(父子組件之間的通信-事件總線)</h3>
<child1 />
<child2 />
</div>
`
})
</script>
</html>
第四種,provide
和 inject
库快,適用于根組件和后代組件通信
在父組件中通過(guò) provider 來(lái)提供屬性摸袁,然后在子組件中通過(guò) inject 來(lái)注入變量。不論子組件有多深义屏,只要調(diào)用了 inject 那么就可以注入在 provider 中提供的數(shù)據(jù)靠汁,而不是局限于只能從當(dāng)前父組件的 prop 屬性來(lái)獲取數(shù)據(jù),只要在父組件的生命周期內(nèi)闽铐,子組件都可以調(diào)用蝶怔。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Vue組件間的通信</title>
</head>
<body>
<div id="app">
<c1 />
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
Vue.component('c1', {
template: `
<div>
<h3>組件1</h3>
<p>組件1-{{ rootMsg }}</p>
<c11 />
</div>
`,
provide: {
c1Msg: '組件1信息'
},
inject: ['rootMsg']
})
Vue.component('c11', {
template: `
<div>
<h3>組件1-1</h3>
<p>組件1-1-{{ c1Msg }}</p>
<c111 />
</div>
`,
inject: ['c1Msg']
})
Vue.component('c111', {
template: `
<div>
<h3>組件1-1-1</h3>
<p>組件1-1-1{{ c1Msg }}</p>
</div>
`,
inject: ['c1Msg']
})
const app = new Vue({
el: '#app',
provide: {
rootMsg: '根信息'
}
})
</script>
</html>
第五種 v-model,適用于父子組件的通信
v-model
用于父子組件之間的通信,體現(xiàn)的是v-model
的實(shí)現(xiàn)原理:綁定value
阳啥,監(jiān)聽(tīng)input
事件添谊。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Vue組件間的通信</title>
</head>
<body>
<div id="app">
<parent />
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
Vue.component('parent',{
template:`
<div>
<h3>父組件</h3>
<p>父組件==>{{ message }}</p>
<child v-model="message" />
</div>
`,
data(){
return {
message:''
}
}
})
Vue.component('child',{
template:`
<div>
<input type="text" v-model="msg" @input="changeValueHandle" />
</div>
`,
props:{
value:{
type:String //v-model 會(huì)自動(dòng)傳遞一個(gè)字段為 value 的 props 屬性
}
},
data(){
return {
msg:''
}
},
methods:{
changeValueHandle(){
this.$emit('input',this.msg)
}
}
})
const app = new Vue({
el: '#app'
})
</script>
</html>
第六種 $parent
和$children
,適用于父子組件之間的通信
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Vue組件間的通信</title>
</head>
<body>
<div id="app">
<parent />
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
Vue.component('parent', {
template: `
<div>
<h2>父組件</h2>
{{ childMsgFromParent }}
<button @click="getChildMsgHandle">獲取子組件信息</button>
<child />
</div>
`,
data() {
return {
parentMsg: '父組件的信息',
childMsgFromParent:''
}
},
methods: {
getChildMsgHandle() {
this.childMsgFromParent = this.$children[0].childMsg
console.log(this.$children[0].childMsg)
}
}
})
Vue.component('child', {
template: `
<div>
<h3>子組件</h3>
{{ parentMsgFromChild }}
<button @click="getParentMsgHandle">獲取父組件信息</button>
</div>
`,
data() {
return {
childMsg: '子組件信息',
parentMsgFromChild:''
}
},
methods: {
getParentMsgHandle() {
this.parentMsgFromChild = this.$parent.parentMsg
console.log(this.$parent.parentMsg)
}
}
})
const app = new Vue({
el: '#app'
})
</script>
</html>