vue框架中懒棉,組件的概念貫穿始終,項目里所有的頁面都是一個組件览绿,它有相對獨立的作用域策严,它們之間如果需要進行數(shù)據(jù)傳遞,就要符合一定的規(guī)則饿敲,比如父子組件傳值妻导,兄弟組件傳值,隔代組件傳值(它們之間隔了好幾個組件)接下來我就講解一下這些組件之間如何傳值:
1. 父組件給子組件傳值(props)
新建src/views/father.vue文件怀各,做為父組件
father.vue
<template>
<div class="father">
</div>
</template>
<script>
export default {
data () {
return {
}
}
}
</script>
新建src/components/children.vue文件作為子組件
children.vue
<template>
<div class="children">
<ul>
<li v-for="(item, index) in userList" :key="index">{{item}}</li>
</ul>
</div>
</template>
<script>
export default {
props: {
userList: {
type: Array,
required: true
}
},
data () {
return {
}
}
}
</script>
注意到子組件里有一個props屬性倔韭,這里來接收父組件傳遞過來的值
父組件里引用子組件并且傳值給子組件,代碼如下:
father.vue
<template>
<div class="father">
<children-item :userList="userList"></children-item>
</div>
</template>
<script>
import childrenItem from '_c/children' // 引入子組件
export default {
components: {childrenItem}, // 調(diào)用子組件
data () {
return {
userList: ['張三', '李四', '王二'] // 傳值給子組件
}
}
}
</script>
2. 子組件給父組件傳值($emit)
在子組件children.vue里面增加一個方法sendData
children.vue
<template>
<div class="children">
<ul>
<li v-for="(item, index) in userList" :key="index">{{item}}</li>
</ul>
<button @click="sendData">向父組件傳值:3</button>
</div>
</template>
<script>
export default {
props: {
userList: {
type: Array,
required: true
}
},
data () {
return {
}
},
methods: {
sendData () {
this.$emit('sendData', 3)
}
}
}
</script>
父組件father.vue里面通過事件接收子組件傳遞過來的值
father.vue
<template>
<div class="father">
<children-item :userList="userList" @sendData="getData"></children-item>
<p>{{value}}</p>
</div>
</template>
<script>
import childrenItem from '_c/children'
export default {
components: {childrenItem},
data () {
return {
userList: ['張三', '李四', '王二'],
value: 0
}
},
methods: {
getData (val) {
// 接收子組件傳遞過來的值
this.value = val
}
}
}
</script>
3. 兄弟組件或隔代組件傳值(on)
這種方法通過一個空的Vue實例作為中央事件總線(事件中心)瓢对,用它來觸發(fā)事件和監(jiān)聽事件,巧妙而輕量地實現(xiàn)了任何組件間的通信寿酌,包括父子、兄弟硕蛹、跨級醇疼。但是我們的項目比較大時,可以選擇更好的狀態(tài)管理vuex法焰。
新建src/bus/index.js文件:
import Vue from 'vue'
const Bus = new Vue()
export default Bus
在main.js里面引入:
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import Bus from './bus/index' // 引入總線
Vue.config.productionTip = false
Vue.prototype.$bus = Bus // 使用總線
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
新建src/components/brother1.vue和brother2.vue文件做為兄弟組件秧荆,讓brother1給brother2傳值
brother1.vue
<template>
<div class="brother1">
<h3>兄弟組件1</h3>
<button @click="sendData">兄弟組件1</button>
</div>
</template>
<script>
export default {
data () {
return {
}
},
methods: {
sendData () {
this.$bus.$emit('sendData', 1) // 發(fā)送
}
}
}
</script>
brother2.vue
<template>
<div class="brother2">
<h3>兄弟組件2</h3>
<p>{{value}}</p>
</div>
</template>
<script>
export default {
data () {
return {
value: null
}
},
methods: {
},
mounted () {
this.$bus.$on('sendData', val => { // 接收
console.log(val)
this.value = val
})
}
}
</script>
4. listeners
這種方式我在項目里沒有用過,它是Vue2.4版本中新增的內(nèi)容壶栋,以后可以在項目里用一用辰如,接下來的例子我就照著網(wǎng)上給的教程敲一遍,演示一下如何使用:
這里需要新建一個子組件贵试,src\components\children2.vue
建好以后琉兜,再來改造下src\views\father.vue這個組件,還有src\components\children.vue這個組件
father.vue
<template>
<div class="father">
<div style="width: 300px;height: 300px;border: 1px solid red;margin: 0 auto;">
<h4>父組件</h4>
<children-item></children-item>
</div>
</div>
</template>
<script>
import childrenItem from '_c/children'
export default {
components: {childrenItem},
data () {
return {
}
},
methods: {
}
}
</script>
<style lang="less" scoped>
.father {
height: 300px;
background: gold;
}
</style>
children.vue
<template>
<div class="children" style="margin: 30px;border:1px solid black;height: calc(100% - 90px);">
<h4>子組件-1</h4>
<children-item2></children-item2>
</div>
</template>
<script>
const childrenItem2 = () => import('_c/children2')
export default {
components: {childrenItem2},
data () {
return {
}
},
methods: {
}
}
</script>
children2.vue
<template>
<div class="children" style="margin: 30px;border:1px solid black;height: calc(100% - 90px);">
<h4>子組件-2</h4>
</div>
</template>
<script>
export default {
data () {
return {
}
},
methods: {
}
}
</script>
這樣做的目的毙玻,使它們之間的關(guān)系是這樣的:
father(a)組件引入children(b)做為子組件豌蟋,children(b)組件引入children2(c)組件做為子組件
father組件(A)--子--children組件(B)--子--children2組件(C)
b是a的子組件,c又是b的子組件桑滩,這樣形成一個嵌套的關(guān)系梧疲,現(xiàn)在有一個需求,需要a組件把值直接傳遞給c組件运准,有幾種解決方法呢幌氮?
- vuex(大材小用)
- a先通過props把值傳遞給b,b再通過props將值傳遞給c(容易出錯)
- 利用上面講的事件總線$bus(多人合作開發(fā)時胁澳,代碼維護性較低)
所以建議使用listeners
假如在父組件a中该互,有name1和name2兩個值需要傳遞給子組件b
a組件
<template>
<div class="father">
<div style="width: 300px;height: 300px;border: 1px solid red;margin: 0 auto;">
<h4>父組件</h4>
<children-item :name1="name1" :name2="name2"></children-item>
</div>
</div>
</template>
<script>
import childrenItem from '_c/children'
export default {
components: {childrenItem},
data () {
return {
name1: '張三',
name2: '李四'
}
},
methods: {
}
}
</script>
子組件b拿到值以后通過$attrs
再傳遞給c組件
b組件
<template>
<div class="children" style="margin: 30px;border:1px solid black;height: calc(100% - 100px);">
<h4>子組件-1</h4>
{{$attrs}}
<children-item2 v-bind="$attrs"></children-item2>
</div>
</template>
<script>
const childrenItem2 = () => import('_c/children2')
export default {
components: {childrenItem2},
inheritAttrs: false,
data () {
return {
}
},
methods: {
}
}
</script>
c組件里這樣獲取這兩個值:
c組件
<template>
<div class="children" style="margin: 30px;border:1px solid black;height: calc(100% - 90px);">
<h4>子組件-2</h4>
<span>{{name1}}</span>
<span>{{name2}}</span>
</div>
</template>
<script>
export default {
props: ['name1', 'name2'],
inheritAttrs: true,
data () {
return {
}
},
methods: {
}
}
</script>
假如b組件通過props接收了name1
這個值,那么c組件就不會接收到name1
這個值了
b組件
<template>
<div class="children" style="margin: 30px;border:1px solid black;height: calc(100% - 100px);">
<h4>子組件-1</h4>
{{$attrs}}
<children-item2 v-bind="$attrs"></children-item2>
</div>
</template>
<script>
const childrenItem2 = () => import('_c/children2')
export default {
components: {childrenItem2},
props: ['name1'],
inheritAttrs: false,
data () {
return {
}
},
methods: {
}
}
</script>
c組件
<template>
<div class="children" style="margin: 30px;border:1px solid black;height: calc(100% - 90px);">
<h4>子組件-2</h4>
<span>{{name2}}</span>
</div>
</template>
<script>
export default {
props: ['name2'],
inheritAttrs: true,
data () {
return {
}
},
methods: {
}
}
</script>
上面講的是如何將a組件的值傳遞給c組件韭畸,這是往下傳遞的宇智,那么如何將c組件的值傳遞給a組件蔓搞,往上傳遞呢?
c組件
<template>
<div class="children" style="margin: 30px;border:1px solid black;height: calc(100% - 90px);">
<h4>子組件-2</h4>
<span>{{name2}}</span>
<button @click="sendData">向a組件傳值</button>
</div>
</template>
<script>
export default {
props: ['name2'],
inheritAttrs: true,
data () {
return {
}
},
methods: {
sendData () {
this.$emit('sendData', 1)
}
}
}
</script>
b組件
<template>
<div class="children" style="margin: 30px;border:1px solid black;height: calc(100% - 100px);">
<h4>子組件-1</h4>
{{$attrs}}
<children-item2 v-bind="$attrs" v-on="$listeners"></children-item2>
</div>
</template>
<script>
const childrenItem2 = () => import('_c/children2')
export default {
components: {childrenItem2},
props: ['name1'],
inheritAttrs: false,
data () {
return {
}
},
methods: {
}
}
</script>
a組件
<template>
<div class="father">
<div style="width: 300px;height: 300px;border: 1px solid red;margin: 0 auto;">
<h4>父組件</h4>
<span>{{value}}</span>
<children-item :name1="name1" :name2="name2" @sendData="getData"></children-item>
</div>
</div>
</template>
<script>
import childrenItem from '_c/children'
export default {
components: {childrenItem},
data () {
return {
name1: '張三',
name2: '李四',
value: null
}
},
methods: {
getData (val) {
this.value = val
}
}
}
</script>
從以上代碼示例随橘,我們可以看出listeners的功能喂分,它就像一個橋梁的作用,負責(zé)在a和c直接傳遞和接收數(shù)據(jù)
5. provide/inject
在第四種方式里:listeners机蔗,其實也可以看到使用的局限在于蒲祈,a,b,c這三個組件,必須是層層嵌套的關(guān)系蜒车,通過在b組件里使用listeners讳嘱,讓a和c進行隔代通信
這里講的provide/inject,則更加靈活酿愧,它是vue2.2版本新增內(nèi)容,不論是a,b邀泉,c這種嵌套關(guān)系嬉挡,還是a,b,c,d,e...更深層的嵌套關(guān)系,a組件通過provide分享數(shù)據(jù)汇恤,b,c,d,e...都可以通過inject拿到a組件分享的數(shù)據(jù)
來看一下具體的示例代碼:
我還是拿上面的組件來演示:
src\views\father.vue -- a組件
src\components\children.vue -- b組件
src\components\children2.vue -- c組件
a組件
中使用provide
<template>
<div class="father">
<div style="width: 300px;height: 300px;border: 1px solid red;margin: 0 auto;">
<h4>父組件</h4>
<children-item></children-item>
</div>
</div>
</template>
<script>
import childrenItem from '_c/children'
export default {
components: {childrenItem},
provide () {
return {
name1: this.name1,
name2: this.name2
}
},
data () {
return {
name1: '張三',
name2: '李四',
}
},
methods: {
}
}
</script>
b組件
使用inject
<template>
<div class="children" style="margin: 30px;border:1px solid black;height: calc(100% - 100px);">
<h4>子組件-1</h4>
{{name1}}
{{name2}}
<children-item2></children-item2>
</div>
</template>
<script>
const childrenItem2 = () => import('_c/children2')
export default {
components: {childrenItem2},
inject: ['name1', 'name2'],
data () {
return {
}
},
methods: {
}
}
</script>
c組件
使用inject
<template>
<div class="children" style="margin: 30px;border:1px solid black;height: calc(100% - 90px);">
<h4>子組件-2</h4>
<span>{{name1}}</span>
<span>{{name2}}</span>
</div>
</template>
<script>
export default {
inject: ['name1', 'name2'],
data () {
return {
}
},
methods: {
}
}
</script>
是不是很簡單庞钢,但是!需要注意:provide 和 inject 綁定并不是可響應(yīng)的因谎,也就是說基括,a組件
里修改一個值,后面的組件并不能拿到修改后的值财岔,如:在a組件
里修改name1值:
<template>
<div class="father">
<div style="width: 300px;height: 300px;border: 1px solid red;margin: 0 auto;">
<h4>父組件</h4>
<span>{{name1}}</span>
<span>{{name2}}</span>
<children-item></children-item>
<button @click="changeName1">改變name1值</button>
</div>
</div>
</template>
<script>
import childrenItem from '_c/children'
export default {
components: {childrenItem},
provide () {
return {
name1: this.name1,
name2: this.name2
}
},
data () {
return {
name1: '張三',
name2: '李四',
}
},
methods: {
changeName1 () {
this.name1 = '旺財'
}
}
}
</script>
這name1改變以后风皿,b,c組件里還是修改之前的值
那么有沒有辦法實現(xiàn)數(shù)據(jù)響應(yīng)式呢?有匠璧,看如下代碼示例:
a組件
改造以后相當(dāng)于將整個a組件實例分享出去
<template>
<div class="father">
<div style="width: 300px;height: 300px;border: 1px solid red;margin: 0 auto;">
<h4>父組件</h4>
<span>{{name1}}</span>
<span>{{name2}}</span>
<children-item></children-item>
<button @click="changeName1">改變name1值</button>
</div>
</div>
</template>
<script>
import childrenItem from '_c/children'
export default {
components: {childrenItem},
provide () {
return {
name: this // 將這個組件實例提供給后面的子組件
}
},
data () {
return {
name1: '張三',
name2: '李四',
}
},
methods: {
changeName1 () {
this.name1 = '旺財'
}
}
}
</script>
b組件
<template>
<div class="children" style="margin: 30px;border:1px solid black;height: calc(100% - 100px);">
<h4>子組件-1</h4>
{{name.name1}}
<children-item2></children-item2>
</div>
</template>
<script>
const childrenItem2 = () => import('_c/children2')
export default {
components: {childrenItem2},
inject: {
name: {
default: () => ({})
}
},
data () {
return {
}
},
methods: {
},
}
</script>
c組件
<template>
<div class="children" style="margin: 30px;border:1px solid black;height: calc(100% - 90px);">
<h4>子組件-2</h4>
<span>{{name.name1}}</span>
</div>
</template>
<script>
export default {
inject: {
name: {
default: () => ({})
}
},
data () {
return {
}
},
methods: {
},
}
</script>
以上就可以實現(xiàn)響應(yīng)式的往子組件傳遞數(shù)據(jù)
題外話桐款,我說一下,方法4和方法5夷恍,這些都是不常用的api魔眨,其實在做項目的時候,vue提供給我們的api大概能用到50%可能就已經(jīng)不錯了酿雪,但是我想說的是遏暴,即使不常用的也要知道怎么用,并且做項目的時候用一用比較好
5. children/$refs
使用這3種方式都會得到組件實例指黎,然后就可以直接調(diào)用組件里的方法或者數(shù)據(jù)
同樣還是使用上面的a,b,c三個組件做為例子演示一下:
a組件
<template>
<div class="father">
<div style="width: 300px;height: 300px;border: 1px solid red;margin: 0 auto;">
<h4>父組件</h4>
<span>{{name1}}</span>
<span>{{name2}}</span>
<children-item ref="childrenItem"></children-item>
</div>
</div>
</template>
<script>
import childrenItem from '_c/children'
export default {
components: {childrenItem},
provide () {
return {
name: this
}
},
data () {
return {
name1: '張三',
name2: '李四',
}
},
methods: {
},
mounted () {
console.log(this.$refs.childrenItem.name)
this.$refs.childrenItem.getName()
// 效果相同
console.log(this.$children[0].name)
this.$children[0].getName()
}
}
</script>
b組件
<template>
<div class="children" style="margin: 30px;border:1px solid black;height: calc(100% - 100px);">
<h4>子組件-1</h4>
<children-item2 ref="childrenItem2"></children-item2>
</div>
</template>
<script>
const childrenItem2 = () => import('_c/children2')
export default {
components: {childrenItem2},
data () {
return {
name: '子組件-1'
}
},
methods: {
getName () {
console.log(this.name)
}
},
mounted () {
console.log(this.$parent.name1) // 張三
console.log(this.$refs.childrenItem2.name) // 子組件-2
console.log(this.$children[0].name) // 子組件-2
}
}
</script>
c組件
<template>
<div class="children" style="margin: 30px;border:1px solid black;height: calc(100% - 90px);">
<h4>子組件-2</h4>
</div>
</template>
<script>
export default {
data () {
return {
name: '子組件-2'
}
},
methods: {
getName () {
console.log(this.name)
}
},
mounted () {
console.log(this.$parent.name) // 子組件-1
}
}
</script>