1.组件通信的解决方案
组件通信的方案一般与组件之间的关系有关,组件之间的关系不同,所对应的通信方案也不同。组件之间的关系一般分为两种:1.父子关系,2.非父子关系
(1)父子关系
父子关系的组件之间通信一般使用props和$emit
1.父传子
1.1 给子组件绑定自定义属性,并给其传值
1.2 父组件使用props接收该属性的值
父组件代码示例:
<template>
<!-- 1.给子组件绑定自定义属性,给子组件的自定义属性msg传值myMsg-->
<Son :msg="myMsg"></Son>
</template>
<script>
//导入子组件
import Son from /components/Son
export default {
data(){
return {
myMsg:'hello world!'
}
},
//注册子组件
components:{
Son
}
}
</script>
<style>
</style>
子组件代码示例:
<template>
<!-- 3.使用props接收的msg-->
<div>{{msg}}</div>
</template>
<script>
export default {
// 2.子组件通过props接收传递过来的msg
props:['msg']
}
</script>
<style>
</style>
2.子传父
2.1 子组件使用$emit给父组件发送消息通知
2.2 父组件添加子组件的监听
2.3 监听之后,添加方法处理监听到的数据
父组件代码示例
<template>
<!--2.对子组件传递的消息进行监听,并绑定函数处理传递的值 -->
<Son :msg="myMsg" @changeMsg="handleChange"></Son>
</template>
<script>
//导入子组件
import Son from /components/Son
export default {
data(){
return {
myMsg:'hello world!'
}
},
//注册子组件
components:{
Son
},
methods:{
// 3.将监听到子组件传递过来的新值赋值给父组件的myMsg
handleChange(newMsg){
this.myMsg=newMsg;
}
}
}
</script>
<style>
</style>
子组件代码示例
<template>
<div>
<div>{{msg}}</div>
<button @click="changeFn">修改msg</button>
</div>
</template>
<script>
export default {
props:['msg'],
methods:{
changeFn(){
// 1.通过$emit,向父组件发起通知
this.$emit('changeMsg','newMsg')
}
}
}
</script>
<style>
</style>
(2)非父子关系
非父子关系的组件之间一般才用provide & inject 和enventbus
通用的解决方案Vuex (适用于复杂的业务场景)
2.1 enventbus 事件总线
(1)创建一个都能访问的事件总线(空的vue实例)EventBus.js
import Vue from "vue";
const Bus =new Vue();
export default Bus;
(2)B组件导入事件总线,并利用其发送消息
<template>
<div>B组件
<button @click="send">发送消息</button>
</div>
</template>
<script>
// 导入事件总线
import Bus from '../utils/EnventBus'
export default {
methods:{
send(){
//发送消息
Bus.$emit('sendMsg','I am newMsg')
}
}
}
</script>
<style>
</style>
(3)A组件导入事件总线监听B组件发送的消息,并将消息做回调处理
<template>
<div>BaseA组件:{{msg}}</div>
</template>
<script>
import { util } from 'vue/types/umd'
//导入事件总线
import Bus from '../utils/EnventBus';
export default {
data(){
return {
msg : ''
}
},
created(){
//监听sendMsg方法传递过来的值,并利用回调函数将其赋值给当前的msg
Bus.$on('sendMsg',(msg)=>{
this.msg = msg;
})
}
}
</script>
<style>
</style>
2.2 provide & inject :跨层级共享数据
2.2.1 父组件使用 provide共享数据
export default {
//使用provide共享数据
provide(){
return{
msg:this.msg, //简单类型 非响应式
student:this.student//复杂类型 响应式
}
},
data(){
return {
msg : '',
student:{
id:001,
name:'张三',
grade:3,
class:9,
age:18,
gender:'男'
}
}
}
}
2.2.2 子孙组件使用inject接收数据
<template>
<div>
<!--使用数据进行渲染-->
<span>{{msg}}</span>
<span>{{student.name}}</span>
<span>{{student.age}}</span>
<span>{{student.gender}}</span>
</div>
</template>
export default {
//使用inject接收provide所共享的数据
inject:['msg','student']
}
(3)复杂业务场景 (vuex)
3.1 vuex概述:
vuex是一个vue的状态管理工具。(状态就是数据,vuex可管理vue的通用数据,即多组件 共享的数据)
3.2 使用场景:
(1)某个状态在很多个组件中都要使用,如个人信息
(2)多个组件共同维护同一份数据,如购物车
3.3 优势
(1)数据集中化管理
(2)响应式变化
(3)操作简洁(vuex提供了一些辅助函数简化操作)
3.4 使用步骤
(1)安装vuex插件
使用yarn安装 ,在项目的终端输入 yarn add vuex@3
使用npm安装,在项目的终端输入 npm i vuex@3
(2)新建vuex模块文件
即在src目录下创建一个store文件夹,并新建一个index.js定义vuex
(3)创建仓库 (编写store\index.js代码,代码如下)
// 这里存放的是vuex的核心代码
import Vue from 'vue'
import Vuex from 'vuex'
// 插件安装
Vue.use(Vuex)
// 创建仓库
const store = new Vuex.Store()
// 导出
export default store
(4)将创建的仓库在main.js挂载到Vue中
import Vue from 'vue'
import App from './App.vue'
// 导入store
import store from '@/store/index'
Vue.config.productionTip = false
new Vue({
render: h =>h(App),
// 挂载store
store
}).$mount('#app')
(5)给仓库提供数据
// 创建仓库
const store = new Vuex.Store({
// 在仓库通过state提供数据(所有组件共享)
state:{
name:'张三',
id: 1001
}
})
(6)使用数据
<template>
<div class="header">header
//模版中使用 $store.state.xx获取数据
<div>{{ $store.state.name }}</div>
<div>{{ $store.state.id }}</div>
</div>
</template>
<script>
export default {
created () {
//组件中使用 this.$store.state.xx获取数据
console.log(this.$store.state.name)
}
}
</script>
或者使用vuex的mapState辅助函数获取数据
import { mapState } from 'vuex'
export default {
created () {
console.log(this.$store.state.name)
// 使用vuex的mapState辅助函数获取数据
console.log(mapState(['name', 'id']))
}
(7)修改数据(由于vue默认是单向数据流的,使用数据的组件无法直接更改仓库中的数据,所以需要使用mutations去修改数据)
7.1 定义mutations对象,对象中存放修改state的方法
const store = new Vuex.Store({
// 在仓库的state中提供数据
state: {
name: '张三',
id: 1001,
num: 100,
list: [1,2,3,4,5,6,7,8,9,10]
},
// 定义mutations对象并添加数据的方法
mutations: {
//无参方法
addNum (state) {
state.num++
},
//有参方法,除了本身的state外只能传一个参数,如果需要传多个,可以将参数封装成数组或对象
changeName (state, name) {
state.name = name
},
changeNameAndNum(state,obj){
state.name = obj.name
state.num = obj.num
}
},
//由于mutations是必须同步的(便于监听数据变化和调试),如果需要异步处理数据则需要用到actions
actions: {
//在action中定义一个方法,用于异步修改数据,context是上下文,num是传递的参数
changeNumAsync(context,num){
// 设置定时器 一秒之后修改数据
setTimeout(()=>{
// action中不能直接修改state中的数据,需要调用mutations中的方法
context.commit('changeNum',num)
},1000)
}
},
//getters类似于computed计算属性,依赖于state中的数据进行处理展示
getters: {
// 如需要展示list数组中大于5的元素,则可以在getters中添加以下方法
// 注意 1:第一个参数为state 2. 必须要有返回值,返回值就是getters的值
filterList(state){
return state.list.filter(item => item > 5 )
}
}
})
7.2 组件中提交调用mutations中的方法
//导入vuex中的辅助函数
import {mapState,mapMutations,mapActions,mapGetters} from 'vuex'
// 调用无参方法
this.$store.commit('addNum')
// 调用有参方法
this.$store.commit('changeName','李四')
// mutations不支持传多个参数,如有多个参数,用对象封装传参
this.$store.commit('changeName',{name:'李四',num:'1'})
// mapMutations辅助函数将方法添加到了methods中,可直接用方法名调用,无需this.$store.commit()
this.changeName('李四')
this.addNum('1')
// mapState辅助函数将属性添加到了computed中,可直接使用,无需this.$store.state.name
console.log(this.name)
console.los(this.id)
//需要异步处理数据,调用仓库中actions中的方法
this.$store.dispatch(['changeNumAsync'])
//mapActions辅助函数将actions方法,映射到了methods中,就可以直接调用,无需this.$store.dispatch
this.changeNumAync(1)
// 获取getters中的数据
console.log(this.$store.getters.filterList)
// mapGetters辅助函数将getters中的数据,映射到了computed属性中,可以直接使用
console.log(this.filterList)
methods: {
// 使用mapMutations辅助函数,将仓库提供的mutations中的方法,展开映射到methods中,就可以直接调用
...mapMutations(['changeName','addNum']),
// 使用mapActions辅助函数,将仓库提供的actions中的方法,展开映射到methods中,就可以直接调用
...mapActions('changeNumAsync',1)
},
computed:{
//使用mapState辅助函数,将仓库提供的数据,展开映射到computed属性中,就可以直接使用
...mapState(['name','id']),
//使用mapGetters辅助函数,将仓库中依赖于state的派生属性,映射到computed属性中,就可以直接使用
...mapGetters(['filterList'])
}
(8)分模块
如果所有数据都在store/index.js中的仓库中维护,那么随着数据越来越多将会变得难以维护,可以将数据根据模块进行拆分,最后将这些模块利用modules属性挂载到仓库中。
8.1 ,首先在store中新建一个modules文件夹,不同模块的数据写在不同的js中,如用户模块,写在user.js中。在user.js中定义是state,mutations,getters,actions等等属性
8.2 在store/index.js中导入对应模块的js,并挂载到仓库中
import user from '../store/modules/user'
// 插件安装
Vue.use(Vuex)
// 创建仓库
const store = new Vuex.Store({
// 将user.js中的数据注入到仓库的子模块中
modules: {
user
}
})
8.3 使用user子模块中的中的数据
//方式一 原生js
this.$store.state.user.age
//方式二 使用mapState('模块名',['属性名']) 注册到computed中,直接使用
//注意 使用这种方式需要在user.js中开启命名空间(在user.js export default中加上 namespaced: true)
console.log(this.age)
// 不光是state可以这样使用 getters,actions,mutations都可以这样使用
// this.$store.getters('模块名/属性名')
this.$store.getters(['user/firstName'])
// 使用mapGetters('模块名',['属性名']) 也需开启命名空间
console.log(this.firstName)
// 调用子模块的mutations this.$store.commit('模块名/方法名','额外参数')
this.$store.commit('user/changeName','李四')
//使用mapMutations
this.changeName('李四')
//调用子模块的actions this.$store.dispatch('模块名/方法名','额外参数')
this.$store.dispatch('user/setNameAsync','李四')
// 使用mapActions('模块名',['方法名']) 注册到methods中,可通过方法名直接调用
this.setNameAsync('李四')
methods:{
...mapMutations('user',['changeName']),
...mapActions('user',['setNameAsync'])
},
computed: {
...mapState('user',['age']),
...mapGetters('user',['firstName'])
}
const state ={
userName: '张三',
age: 20,
gender: '男'
}
const mutations = {
changeName(state,newname) {
state.userName = newname
}
}
const gatters = {
firstName(state) {
return state.userName.substr(0,1)
}
const actions = {
setNameAsync(context,newName){
setTimeout(()=>{context.commit('changeName',newName)},1000)
}
}
export default {
// 开启命名空间
namespaced: true,
state,
mutations,
getters,
actions
}