前言
vue的核心就是组件的使用,组件是可复用的vue实例。如果项目中某一个部分需要在多个页面中使用到,我们就可以将这部分代码抽成一个可复用的组件。
然而,组件实例的作用域之间是相互独立的,如果需要把组件之间的数据关联起来,这就需要懂组件之间的通信。
一、父组件向子组件传值(通过props)
父组件通过v-bind绑定变量,子组件通过props方式接收。
例子:在子组件Child.vue中如何获取到父组件App.vue中的数据title: '我是父组件的数据'。
父组件
// App.vue 父组件
<template>
<div id="app">
<h2>父组件:</h2>
<!-- :title 是传到子组件的变量名,便于子组件调用 -->
<!-- title 是父组件中的data的属性值 -->
<Child :title="title"/>
</div>
</template>
<script>
import Child from './components/Child'
export default {
name: 'App',
data () {
return {
title: '我是父组件的数据'
}
},
components: {
Child
}
}
</script>
<style>
</style>
子组件
// Child.vue 子组件
<template>
<div class="child">
<h3>子组件:{{title}}</h3>
</div>
</template>
<script>
export default {
name: 'Child',
// 接收父组件的值
props: {
title: String
}
}
</script>
<style scoped>
</style>
实现效果
总结:这种方式只能由父组件向子组件传递,子组件不能更新父组件内的data。也就是说,当父组件的属性发生变化时,将传递给子组件,但不会反过来,因为props是单向绑定的。
二、子组件向父组件传值(通过$emit)
相当于子组件调用父组件的方法。
子组件通过$emit发射一个方法,父组件通过v-on实现。
例子:当我们点击子组件的调用父组件中的方法按钮时,父组件中的内容我是父组件的内容修改成父组件的内容被修改了,从而实现子组件向父组件传值。
子组件
// Child.vue 子组件
<template>
<div class="child">
<h3>子组件:</h3>
// 定义一个子组件传值的方法 handleClick
<input type="button" value="调用父组件中的方法" @click="handleClick">
</div>
</template>
<script>
export default {
name: 'Child',
data () {
return {
title: '父组件的内容被修改了'
}
},
methods: {
handleClick () {
// titleChanged 自定义事件名
// this.title 需要传给父组件的值
this.$emit("titleChanged", this.title)
}
}
}
</script>
<style scoped>
</style>
父组件
// App.vue 父组件
<template>
<div id="app">
<h3>父组件 -- {{title}}</h3>
<!-- titleChanged 与子组件中自定义事件名保持一致 -->
<!-- updateTitle 方法名,需要接收子组件传递过来的值 -->
<Child @titleChanged="updateTitle"/>
</div>
</template>
<script>
import Child from './components/Child'
export default {
name: 'App',
data () {
return {
title: '我是父组件的内容'
}
},
components: {
Child
},
methods: {
updateTitle (e) {
// e 就是子组件传递过来的值
this.title = e;
}
}
}
</script>
<style>
</style>
实现效果
三、父组件调用子组件的方法或访问数据(通过$ref调用)
例子:在父组件App.vue中访问子组件Child.vue中的title数据和调用childAlert方法。
子组件
// Child.vue
<template>
<div class="child">
</div>
</template>
<script>
export default {
name: 'Child',
data () {
return {
title: '我是子组件的内容'
}
},
methods: {
childAlert () {
window.alert('我是子组件里面的弹窗!')
}
}
}
</script>
<style scoped>
</style>
父组件
// App.vue
<template>
<div id="app">
<Child ref="childRef"/>
</div>
</template>
<script>
import Child from './components/Child'
export default {
name: 'App',
data () {
return {
}
},
components: {
Child
},
mounted () {
// 访问子组件的 title
console.log(this.$refs.childRef.title) // 输出‘我是子组件的内容’
// 调用子组件的 childAlert 方法
this.$refs.childRef.childAlert()
}
}
</script>
<style>
</style>
总结:ref如果直接在普通的DOM元素上使用,引用所指向的就是DOM元素,如果在子组件上使用,引用所指向的就是组件实例。
实现效果
四、非父子组件之间的通信(中央事件总线)
该方法通过一个空的Vue实例作为中央事件总线,才能使用on的数据参数,实现组件通信。
创建一个空的Vue实例文件eventBus.js,也就是一个中央事件总线。
// eventBus.js
import Vue from 'vue'
export default new Vue()
创建A.vue组件,引入eventBus.js文件。
// A.vue 组件
<template>
<div class="a_com">
<h3>A组件:</h3>
<input type="button" value="点击按钮给B组件传递数据" @click="emitBCom">
</div>
</template>
<script>
// 引入空的 vue 实例
import eventBus from '../js/eventBus'
export default {
name: 'A',
data () {
return {
msg: '我是A组件的内容'
}
},
methods: {
emitBCom () {
// bComHandle 自定义事件名,触发一个可以让B组件监听的方法
// this.msg 要传给B组件的值
eventBus.$emit('bComHandle', this.msg)
}
}
}
</script>
<style scoped>
</style>
创建B.vue组件,引入eventBus.js文件。
// B.vue 组件
<template>
<div class="b_com">
<h3>B组件:</h3>
<p>接收A组件传递过来的值:{{msg}}</p>
</div>
</template>
<script>
// 引入空的 vue 实例
import eventBus from '../js/eventBus'
export default {
name: 'B',
data () {
return {
msg: '',
}
},
mounted () {
// 监听A组件的自定义事件
// data 这个data就是A组件传递过来的值
eventBus.$on('bComHandle', data => this.msg = data)
}
}
</script>
<style scoped>
</style>
在App.vue中引入A.vue和B.vue两个组件,并挂载到页面上。
// App.vue
<template>
<div id="app">
<a-com />
<b-com />
</div>
</template>
<script>
const ACom = () => import('./components/A')
const BCom = () => import('./components/B')
export default {
name: 'App',
components: {
ACom,
BCom
}
}
</script>
<style>
</style>
整个过程的步骤:
新建一个空的Vue实例文件eventBus.js;
在组件中引入定义的实例;
通过emit(自定义事件名称,要传递的值);
把传递过来的自定义事件通过on(自定义事件名称, () => {})。
总结:这种只用一个Vue实例来作为中央事件总线来管理非父子组件通信的方法只适用于通信需求简单一点的项目,对于更加复杂的情况,需要使用Vue提供的状态管理模式Vuex来进行处理。
实现效果
五、子组件调用父组件的方法或访问数据(另外一种方法:通过$parent)
例子:在子组件Child.vue中访问父组件App.vue中的msg数据和调用show方法。
父组件
// App.vue
<template>
<div id="app">
<child />
</div>
</template>
<script>
const Child = () => import('./components/Child')
export default {
name: 'App',
data () {
return {
msg: '我是父组件中的内容'
}
},
methods: {
show () {
console.log('我是父组件的方法!')
}
},
components: {
Child
}
}
</script>
<style>
</style>
子组件
// Child.vue
<template>
<div class="child">
<h3>我是子组件:</h3>
<p>访问父组件中的msg数据:{{msg}}</p>
</div>
</template>
<script>
export default {
name: 'Child',
data () {
return {
msg: ''
}
},
mounted () {
// 访问父组件中的 msg 数据
this.msg = this.$parent.msg
// 调用父组件中的 show 方法
this.$parent.show()
}
}
</script>
<style scoped>
</style>
总结:用此方法前提得知道父组件是谁,如果项目中组件嵌套非常多的话,不推荐使用这个方法!
实现效果
六、跨级组件间的通信(通过provide/inject)
这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。
provide选项应该是:一个对象或返回一个对象的函数。该对象包含可注入其子孙的属性。
inject选项应该是:一个字符串数组,或一个对象,对象的key是本地的绑定名,value是:在可用的注入内容中搜索用的key(字符串或Symbol),或一个对象,该对象的:from属性是在可用的注入内容中搜索用的 key(字符串或 Symbol),default属性是降级情况下使用的value。
假设我们有两个组件Child.vue和Grandparent.vue,来看一下比较简单的用法:
祖先级组件
// Grandparent.vue组件
<template>
<div class="grandparent">
<child />
</div>
</template>
<script>
const Child = () => import('./Child')
export default {
name: 'Grandparent',
provide: {
name: 'allen'
},
components: {
Child
}
}
</script>
<style scoped>
</style>
子孙级组件
// Child.vue
<template>
<div class="child">
<!-- 获取 Grandparent 组件中的 name 值 -->
<h2>{{gp_name}}</h2>
</div>
</template>
<script>
export default {
name: 'Child',
data () {
return {
gp_name: ''
}
},
inject: ['name'],
mounted () {
this.gp_name = this.name
console.log(this.name) // 输出 allen
}
}
</script>
<style scoped>
</style>
我们在祖先级组件中设置了一个provide:name,值为allen,它的作用就是将name这个变量提供给它的所有子孙级组件。而在子孙级组件中通过inject注入了从上级组件中提供的name变量,那么在子孙级组件中,就可以直接通过this.name来访问了。
提示:provide和inject绑定并不是可响应的。这是刻意为之的。然而,如果你传入了一个可监听的对象,那么其对象的属性还是可响应的。
关于这对选项的深入用法,大家可以去看看官方文档哦!
七、兄弟组件之间的通信(通过Vuex)
Vuex是一个专为Vue.js应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
父组件App.vue
// App.vue
<template>
<div id="app">
<child-a />
<child-b />
</div>
</template>
<script>
const ChildA = () => import('./components/ChildA') // 导入 ChildA 组件
const ChildB = () => import('./components/ChildB') // 导入 ChildB 组件
export default {
name: 'App',
components: {
ChildA,
ChildB
}
}
</script>
<style>
</style>
子组件ChildA.vue
// ChildA.vue
<template>
<div class="child-a">
<h3>A组件:</h3>
<input type="button" value="点击按钮让B组件接收数据" @click="AComHandler">
<p>B组件的数据:{{bmsg}}</p>
</div>
</template>
<script>
export default {
name: 'ChildA',
data () {
return {
amsg: '我是A组件的内容'
}
},
computed: {
bmsg () {
return this.$store.state.BComMsg
}
},
methods: {
AComHandler () {
this.$store.commit('transferAComMsg', {
AComMsg: this.amsg
})
}
}
}
</script>
<style scoped>
</style>
子组件ChildB.vue
// ChildB.vue
<template>
<div class="child-b">
<h3>B组件:</h3>
<input type="button" value="点击按钮让A组件接收到数据" @click="AComHandler">
<p>A组件的数据:{{amsg}}</p>
</div>
</template>
<script>
export default {
name: 'ChildB',
data () {
return {
bmsg: '我是B组件的内容'
}
},
computed: {
amsg () {
return this.$store.state.AComMsg
}
},
methods: {
AComHandler () {
this.$store.commit('transferBComMsg', {
BComMsg: this.bmsg
})
}
}
}
</script>
<style scoped>
</style>
引入vuex模块
> npm install vuex --save
在src文件夹下创建store文件夹,并创建index.js文件
// index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const store = new Vuex.Store({
state: {
// 初始化A组件和B组件的数据
AComMsg: '',
BComMsg: ''
},
mutations: {
// 将A组件数据存放到state中
transferAComMsg (state, payload) {
state.AComMsg = payload.AComMsg
},
// 将B组件数据存放到state中
transferBComMsg (state, payload) {
state.BComMsg = payload.BComMsg
}
}
})
export default store
在main.js导入
// main.js
import Vue from 'vue'
import App from './App'
import store from './store/index'
Vue.config.productionTip = false
new Vue({
el: '#app',
store,
render: h => h(App)
})