组件间通信
Vue也是从基础开始学起,边学边整理,同类文章很多,本文仅供参考。
如有不到之处,请指正。
组件关系:A/B、B/C、B/D为父子关系; C/D为兄弟关系; A/C为隔代关系;
其中隔代关系可能隔多代;
文章目录
1.父子组件通信
A/B、B/C、B/D 均属于父子组件,实现父子组件可以通过以下方式:
- 父组件通过props绑定数据向子组件传递,子组件通过$emit向父组件传递。
- 使用 p a r e n t 获 取 当 前 组 件 的 父 组 件 ( 如 果 存 在 ) , 使 用 parent获取当前组件的父组件(如果存在),使用 parent获取当前组件的父组件(如果存在),使用children获取当前组件所有子组件返回数组。官方API提示,返回的子组件不保证顺序,也不是响应式的。
- 使用provide/inject,父组件通过provide提供变量,在所有需要改变量的子组件或者孙子组件中通过inject注入即可使用。该方式多用于组件库开发,详情可以扒拉elementUI等开源组件库,不适用普通的业务组件开发。
- 使用 a t t r s / attrs/ attrs/listeners,配合inheritAttrs。(更适合隔代组件)
- ref使用在组件上,则返回组件实例,可以访问组件的data以及methods等;如果用在普通DOM元素上,则返回DOM节点
1.props/$emit🌟🌟
2.provide/inject
3.$ref
4.$parent/$children
5.$attrs/$listeners
2.兄弟组件通信
C/D 属于兄弟组件,要实现通信可以通过以下形式(不考虑是否合理):
- 使用eventBus事件总线,相对于1来说事件总线更为合适,但是代码维护性较差,可读性较低。不适合较大项目。
- 使用Vuex进行状态管理,如果仅仅是传递数据,不做异步处理,有显得冗余复杂。
1.eventBus
2.vuex
3.隔代组件通信
A/C、A/D 属于隔代组件,要实现通信可以通过以下形式(不考虑是否合理):
- A.vue使用props绑定数据并逐级传递,C.vue/D.vue 接收数据,如果C.vue/D.vue发生数据变化,则需要通过事件逐级返回通知A.vue。
- 使用eventBus事件总线,相对于1来说事件总线更为合适,但是代码维护性较差,可读性较低。不适合较大项目。
- 使用Vuex进行状态管理,如果仅仅是传递数据,不做异步处理,有显得冗余复杂。
- 使用 a t t r s / attrs/ attrs/listeners,配合inheritAttrs
- 使用provide/inject,该方式多用于组件库开发,详情可以扒拉elementUI等开源组件库,不适用普通的业务组件开发
1.$attrs/$listeners
2.provide/inject
3.vuex
4.eventBus
代码实例
1.props/$emit
<!-- father -->
<template>
<div class="about">
<div>{{msg}}</div>
<children title="我来自父组件" @from-child="handler"></children>
</div>
</template>
<script lang="ts">
import { Vue, Component, Prop } from "vue-property-decorator";
import Children from "../views/Children.vue";
@Component({
components: {
Children,
GrandSon
}
})
export default class Father extends Vue {
private msg:string = "default";
handler($event: any) {
this.msg = $event;
}
}
</script>
<!-- children -->
<template>
<div>
<button @click="handler">向父组件传值</button>
<div>{{title}}</div>
</div>
</template>
<script lang="ts">
import { Vue, Component, Prop } from "vue-property-decorator";
@Component({})
export default class Children extends Vue {
@Prop() title!: string;
handler() {
this.$emit('from-child', '我来自子组件')
}
}
</script>
2.$refs
<!-- father -->
<template>
<div class="about">
<div>{{msg}}</div>
<!-- 通过$refs通信 -->
<children title="我来自父组件" ref="children"></children>
</div>
</template>
<script lang="ts">
import { Vue, Component, Prop } from "vue-property-decorator";
import Children from "../views/Children.vue";
@Component({
components: {
Children,
GrandSon
}
})
export default class Father extends Vue {
msg = "我是父组件"
mounted () {
// 通过$refs获取子组件实例,从而可以获取data、methods等
this.$refs.children.getProps("通过refs来自父组件");
}
}
</script>
<!-- children -->
<template>
<div>
<button>向父组件传值</button>
<div>{{title}}</div>
</div>
</template>
<script lang="ts">
import { Vue, Component, Prop } from "vue-property-decorator";
@Component({})
export default class Children extends Vue {
title = "我是子组件";
getProps(val: string) {
this.title = val;
}
}
</script>
3.$parent/$children
<!-- father -->
<template>
<div class="about">
<div>{{msg}}</div>
<!-- $parent/$children -->
<button @click="change">点击修改子组件内容</button>
<children></children>
</div>
</template>
<script lang="ts">
import { Vue, Component, Prop } from "vue-property-decorator";
import Children from "../views/Children.vue";
@Component({
components: {
Children,
GrandSon
}
})
export default class About extends Vue {
msg = "我是父组件"
title = "我是父组件的title"
change() {
// $children 返回子组件的数组
this.$children[0].modifyMsg();
}
}
</script>
<!-- children -->
<template>
<div>
<div>{{title}}</div>
</div>
</template>
<script lang="ts">
import { Vue, Component, Prop } from "vue-property-decorator";
@Component({})
export default class Children extends Vue {
title = "我是子组件";
modifyMsg(){
this.title = this.$parent.$data.title;
}
}
</script>
4.$attrs/$listeners
<!-- father -->
<template>
<div class="about">
<div>{{msg}}</div>
<children :title="title" attr1="attr1" attr2="attr2" @receivefromgrandson="handler"></children>
</div>
</template>
<script lang="ts">
import { Vue, Component, Prop } from "vue-property-decorator";
import Children from "../views/Children.vue";
import GrandSon from "../views/Grandson.vue";
@Component({
components: {
Children,
GrandSon
},
})
export default class About extends Vue {
msg = "我是父组件"
title = "我是父组件的title"
handler($event: string) {
this.msg = $event;
}
}
</script>
<!-- son -->
<template>
<div>
<!-- $inheritAttrs没有被props注册的prop -->
<div v-bind="$attrs">{{ title }}</div>
<!-- v-on="$listeners"相当于桥梁,如果有重孙子组件,甚至嵌套更多层组件,那么中间层组件都要使用v-on,这样内从组件才能通知祖先组件进行更新 -->
<children2 v-bind="$attrs" v-on="$listeners"></children2>
</div>
</template>
<script lang="ts">
import { Vue, Component, Prop } from "vue-property-decorator";
import children2 from "../views/Grandson.vue";
@Component({
// false表示不会将未注册的props作为html属性展示在DOM节点上
inheritAttrs: false,
components: {
children2
}
})
export default class Children extends Vue {
@Prop() title!:string;
created() {
console.log('子组件',this.$listeners);
}
}
</script>
<!-- grandson -->
<template>
<div>
<span>我是孙子组件:</span>
{{ $attrs }}
<button @click="sendtograndfather">点击隔代发送通知</button>
</div>
</template>
<script lang="ts">
import { Vue, Component, Prop } from "vue-property-decorator";
@Component({
inheritAttrs: false
})
export default class Grandson extends Vue {
// component
created() {
console.log("孙子组件", this.$listeners);
}
sendtograndfather() {
this.$emit("receivefromgrandson", "来自孙子组件的更新通知");
}
}
</script>
5.provide/inject
<!-- father -->
<template>
<div class="about">
<div>{{msg}}</div>
<children :title="title" attr1="attr1" attr2="attr2" @receivefromgrandson="handler"></children>
</div>
</template>
<script lang="ts">
import { Vue, Component, Prop, Provide } from "vue-property-decorator";
import Children from "../views/Children.vue";
import GrandSon from "../views/Grandson.vue";
@Component({
components: {
Children,
GrandSon
},
})
export default class About extends Vue {
msg = "我是父组件"
title = "我是父组件的title"
user = {name: "admin", age: 20}
// 通过Provide向内部组件传递
@Provide() formdata = {
user: this.user
}
handler($event: string) {
this.msg = $event;
}
}
</script>
<!-- children -->
<template>
<div>
<!-- $inheritAttrs没有被props注册的prop -->
<div v-bind="$attrs">{{ title }}</div>
<children2 v-bind="$attrs" v-on="$listeners"></children2>
</div>
</template>
<script lang="ts">
import { Vue, Component, Prop,Inject } from "vue-property-decorator";
import children2 from "../views/Grandson.vue";
@Component({
// false表示不会将未注册的props作为html属性展示在DOM节点上
inheritAttrs: false,
components: {
children2
}
})
export default class Children extends Vue {
@Prop() title!:string;
// 内部子组件通过Inject注入
@Inject() formdata!: object
created() {
console.log('子组件',this.$listeners);
console.log('获取注入的user', this.formdata)
}
}
</script>
6.eventBus
<!-- 调用端 -->
<template>
<div>
<!-- $inheritAttrs没有被props注册的prop -->
<div v-bind="$attrs">{{ title }}</div>
<button @click="handler">通过eventBus通信</button>
<children2 v-bind="$attrs" v-on="$listeners"></children2>
</div>
</template>
<script lang="ts">
import { Vue, Component, Prop,Inject } from "vue-property-decorator";
import children2 from "../views/Grandson.vue";
// 引入bus!!!!!!!!!!!!!!!!!!!!!
import bus from "../util/bus.js"
@Component({
// false表示不会将未注册的props作为html属性展示在DOM节点上
inheritAttrs: false,
components: {
children2
}
})
export default class Children extends Vue {
@Prop() title!:string;
created() {
console.log('子组件',this.$listeners);
}
handler() {
// 通过bus发起事件
bus.$emit("test", "通过bus通信")
}
}
</script>
<!-- 接收端 -->
<template>
<div class="about">
<div>{{ msg }}</div>
<children
:title="title"
attr1="attr1"
attr2="attr2"
@receivefromgrandson="handler"
></children>
</div>
</template>
<script lang="ts">
import { Vue, Component, Prop,Provide } from "vue-property-decorator";
import Children from "../views/Children.vue";
import bus from "../util/bus.js"
@Component({
components: {
Children
},
})
export default class About extends Vue {
msg = "我是父组件"
title = "我是父组件的title"
mounted () {
let that = this;
// 接收端通过$on监听指定的时间名称
bus.$on("test", function(data) {
that.msg = data;
})
}
handler($event: string) {
this.msg = $event;
}
}
</script>
7.vuex
state 存放状态
mutations 操作state
getters 处理state成员并返回
actions 异步操作处理state
modules 模块化
如果vue组件中存在异步操作,需要通过dispatch派发actions中的方法,在actions的方法中commit提交mutations,从而修改state中的数据状态,保证数据的同步。
this.$store.dispatch('add', 10)
如果不存在异步操作,可以在组件中直接commit提交mutations。
this.$store.commit('add');
注意:不可以直接修改state中的数据状态。
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
counter: 0
},
mutations: {
// payload是传递进来的参数
add(state, payload) {
state.counter++;
state.counter += payload;
}
},
actions: {
// 第一个参数是vuex上下文
// payload是传递进来的参数{}
add({ commit, dispatch }, payload) {
return new Promise((resolve, reject) => {
setTimeout(() => {
commit('add', payload)
}, 1000);
});
}
},
// 模块化
modules: {
}
})
模块化就是根据业务创建不同的模块,通过modules统一引入。模块化后需要加上命名空间进行访问!!!
import Vue from 'vue'
import Vuex from 'vuex'
// 引入子模块的vuex
import counter from "./counter";
Vue.use(Vuex)
export default new Vuex.Store({
// 模块化
modules: {
counter
},
strict: true
})
// counter.js
export default {
// 设置独立的命名空间,避免命名冲突
namespaced: true,
state: {
counter: 0
},
mutations: {
// payload是传递进来的参数
add(state, payload) {
state.counter++;
state.counter += payload;
}
},
actions: {
// 第一个参数是vuex上下文
// payload是传递进来的参数{}
add({ commit, dispatch }, payload) {
return new Promise((resolve, reject) => {
setTimeout(() => {
commit('add', payload)
}, 1000);
});
}
}
}
使用了模块化后访问state或者异步处理调用actions等都需要加上对应的命名空间:
<!-- 访问state -->
{{$store.state.counter.counter}}
this.$store.dispatch('counter/add', {})
模块化后导致了访问store的路径很繁琐,所以需要将访问路径进行映射,简化写法:
import { mapState, mapMutations, mapActions } from "vuex";
<!-- 映射state -->
<template>
<div>
<div>我是父组件,当前计数:{{counter}}</div>
<children1></children1>
</div>
</template>
<script lang="ts">
import { Vue, Component, Prop } from "vue-property-decorator";
import children1 from "../views/Children.vue";
import { mapState, mapMutations, mapActions } from "vuex";
@Component({
components: {
children1
},
// 通过...mapState设定好指定的命名空间,讲该命名空间下的state返回。
computed: {
...mapState('counter', {
counter: state => state.counter
})
},
})
export default class NAME extends Vue {
// component
}
</script>
<!-- 映射actions -->
<template>
<div>
<button @click="handler">我是子组件,点击更新计数器</button>
<children2 v-bind="$attrs" v-on="$listeners"></children2>
</div>
</template>
<script lang="ts">
import { Vue, Component, Prop,Inject } from "vue-property-decorator";
import children2 from "../views/Grandson.vue";
import { mapActions } from "vuex";
@Component({
inheritAttrs: false,
components: {
children2
},
methods: {
// 将actions或者mutations中的方法结构放到当前上下文中
...mapActions('counter', ['add'])
},
})
export default class Children extends Vue {
handler() {
// this.$store.commit('add', 10);
// 对比模块化以后的映射actions方法。mutations 中的映射方式一样
// this.$store.dispatch('counter/add', 200)
this['add'](200);
}
}
</script>
vuex部分写的有点乱,没有办法直接跑起来。
其他部分可以直接测试。