Vue组件间通信完整篇
本文提到的Vue组件间通信前六种通常是大家熟知的,后面的几种是组件间通信的高级使用
一、props
使用场景:父子通信
1. 传递数据类型:函数
-
实质子组件想给父亲传递数据
-
在父组件定义好带参函数hander,通过props传递个子组件
<TodoList updateChecked="hander" />
-
在子组件通过props接收函数:
props:['hander']
-
在需要传递参数给父组件的地方使用函数并携带参数过去即可
this.hander(params)
-
2. 传递数据类型:非函数
-
实质就是父亲给子组件传递数据
-
父组件中传递数据给子组件
<TodoList :age="123" name='xzq' />
-
子组件接收数据
-
props: ["name", "age"]
-
props: { name: String, age: Number, },
-
props: { name: { type: String, required: true, // 必传项 }, age: { type: Number, default: 18, // 默认值 } },
-
-
-
注意:当父组件中传递了props时,而子组件没有使用props接收参数,则未被接收的参数会在组件实例的
$attr
上
3. 特殊情况:路由传递props
-
布尔值类型,把路由中params参数映射为组件props数据
-
对象,静态数据,很少用
-
函数,可以把路由中
params
或query
参数映射为组件props数据
二、自定义事件
使用场景:子给父传递数据
1. 在父组件中为子组件绑定自定义事件
-
使用
@
符号,即v-on
指令绑定自定义事件<Student v-on:xzq="getStudentName" /> <Student @xzq="getStudentName" @demo="m1" />
-
使用
ref
绑定自定义事件-
父组件中为子组件打上
ref
标签<!-- .native:指定click事件是原生的事件 --> <Student ref="student" @click.native="show" />
-
在父组件的
mounted
生命周期中,使用$on
绑定自定义事件this.$refs.student.$on("xzq", (name, ...params) => {···}
-
2.子组件中触发自定义事件并传递参数
-
在需要传递数据给父组件的地方,通过
$emit
触发自定义事件this.$emit("xzq", this.name, 666, 999, 888);
3. 卸载自定义事件
-
卸载自定义事件
在组件销毁前(beforeDestroy)使用
$off
卸载自定义事件this.$off("xzq"); //解绑一个自定义事件
三 、$bus全局事件总线
使用场景:任意组件间通讯都适用
1. 原理
-
组件实例(vc)的原型(
__proto__
)的原型(__proto__
)是Vue的原型对象(Vue.prototype)- 所有的组件对象都必须能看见他
- 将
$bus
挂载到Vue.prototype上,所以组件都可以访问到$bus
- 将
- 这个对象必须能够使用
$on $emit $off
方法去绑定、触发和解绑事件- Vue实例(vm)
- 所有的组件对象都必须能看见他
2. 定义全局事件总线
-
在
main.js
中,定义全局事件总线beforeCreate() { Vue.prototype.$bus = this //最标准的安装全局事件总线 },
3. 使用全局事件总线
-
提供数据:
this.$bus.$emit('xxx',data)
-
接收数据:A组件想接收数据,则在A组件中给
$bus
使用$on
绑定自定义事件,事件的回调留在A组件上this.$bus.$on('xxx',(data)=>{···})
4. 解绑当前组件所用到的事件
-
使用
$off
this.$bus.$off("xxx");
四、pubsub-js【发布订阅消息】
在vue中根本不用,React常用
使用场合:任意组件间通讯都适用
1. 安装pubsub-js
npm i pubsub-js
2. 引入
import pubsub from 'pubsub-js'
3. 提供数据(发布消息)
pubsub.publish('xxx',data)
4. 接收数据
接收数据:A组件想接收数据,则在A组件中订阅消息,订阅的回调留在A组件自身
this.pid = pubsub.subscribe('xxx',(data)=>{···})
5. 在beforeDestroy中取消订阅
pubsub.unsubscribe(this.pid)
五、Vuex
使用场景:任意组件间通讯均适用
比较适合的场景:多个组件依赖于同一状态,来自不同组件的行为需要变更同一状态
1. 工作原理
- state:存储数据
- actions:响应组件的动作,可以进行异步操作,如发送AJAX请求
- mutations:用于操作state中的数据
- getters:相当于计算属性,根据的是state中的数据
2. 搭建Vuex环境
- 下载安装vuex:
npm i vuex
- 通常在src目录下建立store文件夹用于存储vuex相关对的文件
3. 非模块化Vuex
- index.js形式
import Vue from 'vue'
//引入vuex
import Vuex from 'vuex'
// 使用插件
Vue.use(Vuex)
const actions = {
func(context, value) {
context.commit('FUNC',value)
},
}
const mutations = {
FUNC(state, value) {
state.value = value
},
}
//state - 用于存储数据
const state = {
value: '',
}
//准备getters---用于将state中的数据进行加工
const getters = {
bigValue(state) {
return state.value * 10
}
}
//创建store并暴露
export default new Vuex.Store({
actions: actions,
//重名触发简写
mutations,
state,
getters
})
-
Vuex的基本使用
-
组件中读取state中的数据的方式
🌈 方式一:直接读取
this.$store.state.value
🌈 方式二:mapState
📃 数组形式:
...mapState(['value'])
📃 字符串对象形式:
...mapState({value:'value'})
📃 函数对象形式:
...mapState({ value:state => state.value })
-
组件中读取getters中的数据
🌈 方式一:直接读取
this.$store.getters.bigValue
🌈 方式二:mapGetters (不支持函数对象形式)
📃 数组方式:
...mapGetters(['bigValue'])
📃 字符串对象形式:
...mapGetters({bigValue:'bigValue'})
-
组件中使用actions中的方法
🌈 方式一:dispatch
this.$store.dispatch('func',value)
🌈 方式二:mapActions
📃 数组方式:
...mapActions(['func'])
📃 字符串对象形式:
...mapActions({func:'func'})
📃 函数形式:
...mapActions({func(dispatch,value){dispatch('func',value)}})
-
组件中使用mutations中的方法
🌈 方式一:commit
this.$store.commit('FUNC',value)
🌈 方式二:mapMutations
📃 数组方式:
...mapMutations(['FUNC'])
📃 字符串对象形式:
...mapMutations({FUNC:'FUNC'})
-
若没有网络请求或其他业务逻辑,组件中也可越过actions,直接mutations,即不写dispatch,直接编写commit
-
4. Vuex模块化+命名空间
这样做让代码更好维护,让多种数据分类明确
-
为了解决不同模块命名冲突的问题,将不同模块的
namespaced: true
-
index.js如下
import Vue from 'vue' import Vuex from 'vuex' // 使用插件 Vue.use(Vuex) const countAbout = { namespaced: true, actions: { func(context, value) { context.commit('FUNC',value) }, }, mutations: { FUNC(state, value) { state.value = value }, }, state: { value: '', }, getters: { bigValue(state) { return state.value * 10 } } } const personAbout = { namespaced: true, actions: {···}, mutations: {···}, state: { value: '', }, getters: {···} } //创建store并暴露 export default new Vuex.Store({ modules: { countAbout:countAbout, personAbout } })
-
组件中读取state中的数据的方式
🌈 方式一:直接读取
this.$store.state.personAbout.value
🌈 方式二:mapState
📃 数组形式:
...mapState('personAbout',['value'])
📃 字符串对象形式:
...mapState('personAbout',{value:'value'})
📃 函数对象形式:
...mapState('personAbout',{ value:state => state.value })
-
组件中读取getters中的数据
🌈 方式一:直接读取
this.$store.getters['personAbout/bigValue']
🌈 方式二:mapGetters (不支持函数对象形式)
📃 数组方式:
...mapGetters('personAbout',['bigValue'])
📃 字符串对象形式:
...mapGetters('personAbout',{bigValue:'bigValue'})
-
组件中使用actions中的方法
🌈 方式一:dispatch
this.$store.dispatch('personAbout/func',value)
🌈 方式二:mapActions
📃 数组方式:
...mapActions('personAbout',['func'])
📃 字符串对象形式:
...mapActions('personAbout',{func:'func'})
📃 函数形式:
...mapActions('personAbout',{func(dispatch,value){dispatch('func',value)}})
-
组件中使用mutations中的方法
🌈 方式一:commit
this.$store.commit('personAbout/FUNC',value)
🌈 方式二:mapMutations
📃 数组方式:
...mapMutations('personAbout',['FUNC'])
📃 字符串对象形式:
...mapMutations('personAbout',{FUNC:'FUNC'})
5. main.js中挂在store
//创建vm
new Vue({
el: '#app',
//触发对象简写模式
store,
render: h => h(App),
beforeCreate() {
Vue.prototype.$bus = this
// console.log(this)
},
});
六、插槽slot
使用场景:父子通讯,通常用于传递结构
让父组件可以**向子组件指定位置插入html结构**,也是一种组件间通信的方式
1. 默认插槽
-
父组件中
<Category> <div>html结构</div> </Category>
-
子组件中
<template> <div> <!-- 定义的一个插槽 父组件传递过来的结构将在下面呈现 --> <slot></slot> </div> </template>
2. 具名插槽
-
父组件中
父组件指明放入子组件的哪个插槽 slot=“footer”,如果是template可以写成v-slot:footer
<Category> <template slot="center"> <div>html结构1</div> </template> <template v-slot:footer> <div>html结构2</div> </template> </Category>
-
子组件中
<template> <div> <! -- 定义插槽--> <slot name="center">插槽默认认内容...</slot> <slot name="footer">插槽默认认内容...</slot> </div> </template>
3. 作用域插槽
- scope:用于父组件往子组件插槽,放的html结构接收子组件的数据
- 理解:数据在子组件,但根据==数据生成的结构需要组件的使用者(父组件)==来决定
( games 数据在Category 组件中,但使用数据所遍历出来的结构由App 组件决定)
-
父组件中
<Category> <template scope="scopeData"> <!-- 生成的是ul列表 --> <ul> <li v-for="g in scopeData.games" :key="g">{{g}}</li> </ul> </template> </Category> <Category> <template slot-scope="scopeData"> <!-- 生成的是h4标题 --> <h4 v-for="g in scopeData.games" :key="g">{{g}}</h4> </template> </Category>
-
子组件中
<template> <div> <!-- 数据在子组件中,注意此处不是props,是子组件向父组件传递数据 --> <slot :games="games" ></slot> </div> </template>
-
注意点:
<slot :games="games" ></slot>
中,games不是传递的props,是传递给父组件的scope可以使用scopeData.games
获取
七、v-model *
我们都知道v-model,通常是用来收集表单数据,下面将介绍如何用来实现组件通信
使用场景:用于实现父子数据同步
1. 使用步骤
-
在父组件中绑定好传递的数据,实际上传递的是props属性为value的数据和自定义事件名为input的事件,我们不用自己写这个自定义事件input的回调,它会自动识别并实现数据的更改
<CustomInput v-model="msg2"></CustomInput>
-
子组件中首先使用props接收value,并在相应的地方使用,并通过触发自定义事件input传递数据
<template> <div style="background: #ccc; height: 50px"> <h2>input包装组件</h2> <!-- @input 是原生DOM事件 --> <input type="text" :value="value" @input="$emit('input', $event.target.value)" /> </div> </template> <script type="text/ecmascript-6"> export default { name: 'CustomInput', props:['value'] } </script>
2. v-model实现组件通信的原理
-
父组件中
-
结构:传递props属性为value的数据给子组件,并且在子组件上绑定自定义事件input
<CustomInput :value="msg2" @input="inputHandler"></CustomInput>
-
js:定义好自定义事件input的回调
inputHandler(params){ this.msg2 = params; }
-
-
子组件中
-
props接收传递过来的value,并在对应的地方触发自定义事件input,传递的参数即设置为value的值
<template> <div style="background: #ccc; height: 50px"> <h2>input包装组件</h2> <!-- @input 是原生DOM事件 --> <input type="text" :value="value" @input="$emit('input', $event.target.value)" /> </div> </template> <script type="text/ecmascript-6"> export default { name: 'CustomInput', props:['value'] } </script>
-
八、属性修饰符sync *
该属性可以实现父子组件数据同步
1. 使用
-
父组件中
<Child :money.sync="money"></Child>
-
子组件中:使用props接收money,并在相应的地方触发自定义事件==“update:money”==
<template> <div style="background: #ccc; height: 50px;"> <span>小明每次花100元</span> <button @click="$emit('update:money',money-100)">花钱</button> 爸爸还剩{{money}}元 </div> </template> <script type="text/ecmascript-6"> export default { name: 'Child', props:['money'] } </script>
2. 原理说明
:money.sync
:代表父组件给字符串传递 props【money】 且给当前子组件绑定一个自定义事件(‘update:money’)- 在子组件中触发自定义事件
update:money
,传递的参数就是money的值
九、$attrs和$listeners
1. $attrs
- 组件实例的属性,可以获取到父组件传递的props数据(前提子组件没有通过props接受)
2. $listeners
- 组件实例的属性,可以获取到父组件传递的自定义事件(对象形式呈现)
3. 案例
-
父组件中:handler函数在父组件定义
<!-- 向我们封装的按钮的时候传递相应的参数 @click:自定义事件 --> <hintButton type="warning" icon="el-icon-s-help" size="mini" tip="提示信息" @click="handler"></hintButton>
-
子组件中
<!-- 使用v-bind将所有$attrs的属性绑定到组件上 使用v-on将所有的父组件传过来的自定义事件绑定到组件上 下面v-bind不能用:替换 v-on不能用@替换 --> <el-button v-bind="$attrs" v-on="$listeners">主要按钮</el-button>
十、$ref、$children与$parent
1. $refs
ref也算在一种通信手段:在父组件中可以获取子组件(属性|方法)
- 可以在父组件内部获取子组件实例(VC)—实现父子通信
- 先给子组件打上ref标签:
ref='son'
- 使用子组件的属性:
this.$refs.son.money -= 100
2. $children
- 可以获取当前组件的全部子组件
- 注意点:这个属性在用的时候很少用索引值获取子组件,因为没有办法确定数组里面的元素到底是哪一个子组件,即子组件顺序在$children未知
- 例如:
this.$children.forEach(item => { item.money -= 200 })
3. $parent
- 可以在子组件内部获取唯一的父组件【返回组件实例】
- 例如:
this.$parent.money += 50