前言
在抽离公共组件时,会遇到很多需要组件传值的地方,特别是封装对话框组件。回想最初只会props、$emit的我在遭遇组件二次封装时越写越复杂,越写越看不懂,内心悲痛万分。痛心疾首的我熬夜写了这篇文章,帮助你们掌握组件传值的各种花式,玩弄各种业务需求,高强度对线面试官。是的,我心里装的是谁你们都清楚了吧?没有她,只有你们!
props、$emit
最基本的传值方式,主要用于父子之间,也可以用于爷孙之间(需要父组件做中转)
父子之间
父传子:子组件通过props接收
父组件:
<template>
<div>
<Center :cc="c1"></Center>
</div>
</template>
<script>
import Center from "./center.vue";
export default {
components: {
Center,
},
data() {
return {
c1: "这里是父组件",
};
},
};
</script>
子组件:props里的值使用方式同data(){}
<template>
<div>
{{ cc }}
</div>
</template>
<script>
export default {
props: {
cc: {
type: String,
},
},
components: {
Innner,
},
mounted(){
console.log(this.cc)
}
};
</script>
子传父:
子传父通过子模块$emit注册事件,父模块监听事件来获得数据
子组件html:
<button @click="handleChange">改变cc的值</button>
子组件script
这里 onChange即为注册的事件,后面的字符串为传给父组件的值
handleChange() {
this.$emit("onChange", "这里是子组件的cc");
},
父组件html
父组件通过 @onChange来接受子组件传来的值
<Center :cc="c1" @onChange="handle"></Center>
父组件script
handle(val) {
this.c1 = val;
},
结果:
点击按钮,改变cc的值:
爷孙传值
爷孙之间的传值通过父组件作为中转,outside将值传给center,center再将值传给inner,反之同理
爷组件outside:<Center :aa="a1" ></Center>
父组件center:<Innner :aa="aa"></Innner>
props: { aa: { type: String }, cc: { type: String, }, },
子组件inner:props: { aa: { type: String, default: "haha" }, },
孙爷传值
子组件inner:
<template>
<div>
这里是inner:{{ aa }}
<button @click="$emit('onInner', '这里是来自inner的值')">按钮</button>
</div>
</template>
<script>
export default {
props: {
aa: { type: String, default: "haha" },
},
};
</script>
父组件center:
<template>
<div>
<Innner :aa="aa" @onInner="onCenter"></Innner>
</div>
</template>
<script>
import Innner from "./innner.vue";
export default {
props: {
aa: { type: String },
},
components: {
Innner,
},
methods: {
onCenter(val) {
this.$emit("onCenter", val);
},
},
};
</script>
爷组件outside:
<template>
<div>
<Center :aa="a1" @onCenter="handleChange"></Center>
</div>
</template>
<script>
import Center from "./center.vue";
export default {
components: {
Center,
},
data() {
return {
a1: "这里是outside的值",
};
},
methods: {
handleChange(val) {
this.a1 = val;
},
},
};
</script>
爷孙之间传值本质和父子传值相同。同理,只要你会了props,$emit,你就可以实现任意层数的组件嵌套(狗头滑稽)
$ attrs、$listener
$ attrs、$ listener用来二次封装对话框简直不要太香,哈哈。
$ attrs:包含了父作用域中不作为 prop 被识别 (且获取) 的 attribute 绑定 (class 和 style 除外)。
这里可以理解为,通过父组件中设置v-bind:’$attrs’,子组件将接受爷组件传的值中父组件没有props接受的值。(建议在父组件center中打印this. $ attrs)
爷组件outside:这里outside传给父组件center,aa、bb、cc三个值
<template>
<div>
<Center :aa="a1" :bb="b1" :cc="c1" @onCenter="handleChange"></Center>
</div>
</template>
<script>
import Center from "./center.vue";
export default {
components: {
Center,
},
data() {
return {
c1: "这里是outside组件的c",
a1: "这里是outside的a",
b1: "这里是outside的b",
};
},
};
</script>
父组件center:center中props值只接收了cc的值,将aa、bb的值传递给子组件inner。
<template>
<div>
<Innner v-bind="$attrs" ></Innner>
这里是center:{{ cc }}
</div>
</template>
<script>
import Innner from "./innner.vue";
export default {
props: {
cc: {
type: String,
},
},
components: {
Innner,
},
mounted(){
console.log(this.$attrs)
}
};
</script>
子组件inner:
<template>
<div>
这里是inner的aa的值:{{ aa }}<br />
这里是inner的bb的值:{{ bb }}
</div>
</template>
<script>
export default {
props: {
aa: { type: String, default: "haha" },
bb: { type: String, default: "heihei" },
},
data() {
return {};
},
mounted() {},
};
</script>
<style lang="">
</style>
结果:
$ listerner:包含了爷组件outside中的 (不含 .native 修饰器的) 所有v-on 事件。
它可以通过 v-on=”$listeners” 传入内部组件。简单点来说就是进行爷孙组件之间的 $emit对话。在使用.sync二次封装时不要忘记写它。
爷组件outside:
<template>
<div>
<Center :aa="a1" :bb="b1" :cc="c1" @onChange="handleChange"></Center>
</div>
</template>
<script>
import Center from "./center.vue";
export default {
components: {
Center,
},
data() {
return {
c1: "这里是outside组件的c",
a1: "这里是outside的a",
b1: "这里是outside的b",
};
},
methods: {
handleChange(val) {
this.a1 = val;
},
},
};
</script>
父组件center:
<Innner v-bind="$attrs" v-on="$listeners"></Innner>
孙组件inner:
<template>
<div>
这里是inner的aa的值:{{ aa }} <button @click="handle">修改aa的值</button>
<br />
这里是inner的bb的值:{{ bb }}
</div>
</template>
<script>
export default {
props: {
aa: { type: String, default: "haha" },
bb: { type: String, default: "heihei" },
},
data() {
return {};
},
mounted() {},
methods: {
handle() {
this.$emit("onChange", "修改aa的值为$listeners");
},
},
};
</script>
结果:
点击按钮:
$ parent、$children
$ parent、$children获得的是实例对象,我们需要在实例上获得我们需要的东西
$ parent:当前实例的父实例,如果当前实例有的话。
爷组件outside:
html部分
<Center :aa="a1" :bb="b1" :cc="c1" @onCenter="handleChange"></Center>
script部分
data() {
return {
c1: "这里是outside组件的c",
a1: "这里是outside的a",
b1: "这里是outside的b",
};
},
父组件center :script部分
<script>
import Innner from "./innner.vue";
export default {
props: {
cc: {
type: String,
},
},
components: {
Innner,
},
mounted() {
console.log(this.$parent);
},
};
</script>
这里可以通过this.$parent.a1获得爷组件数据
$children:当前实例的直接子组件。需要注意 $children 并不保证顺序,也不是响应式的。如果你发现自己正在尝试使用 $children 来进行数据绑定,考虑使用一个数组配合 v-for 来生成子组件,并且使用 Array 作为真正的来源。
这里得到的是包含子组件对象的数组
父组件center:script部分
mounted() {
console.log(this.$parent);
console.log(this.$children);
},
同样的,这里可以通过this.$children.aa获得子组件inner的数据
eventBus
EventBus 又称为事件总线。在Vue中可以使用 EventBus 来作为沟通桥梁的概念,就像是所有组件共用相同的事件中心,可以向该中心注册发送事件或接收事件,所以组件都可以上下平行地通知其他组件,但也就是太方便所以若使用不慎,就会造成难以维护的“灾难”,因此才需要更完善的Vuex作为状态管理中心,将通知的概念上升到共享状态层次。
这里可以理解为,组件A在事件总线注册事件,组件B监听事件来实现非父子组件的传值。
初始化
//main.js
Vue.prototype.$EventBus = new Vue()
注册事件:$emit注册事件
//evevtBusA.vue
<template>
<button @click="sendMsg()">开始</button>
</template>
<script>
export default {
methods: {
sendMsg() {
this.$EventBus.$emit("aMsg", "来自A页面的消息");
},
},
};
</script>
监听事件:$on监听
<template>
</template>
<script>
export default {
data() {
return {
msg: "",
};
},
mounted() {
this.$EventBus.$on("aMsg", (msg) => {
// A发送来的消息
console.log("这里是B组件", msg);
});
},
};
</script>
点击开始按钮:
大家都知道vue是单页应用,如果你在某一个页面刷新了之后,与之相关的EventBus会被移除,这样就导致业务走不下去。(能用其他方式就用其他方式吧,我干业务是一次没用到过这个A.A)移出事件监听就不写了,对线面试官知道怎么用事件总线已经够了。详情可以康康这篇文章Vue事件总线(EventBus)使用详细介绍
provide和inject
这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在其上下游关系成立的时间里始终生效。
祖先组件使用provide注入依赖后,不管多深的子代组件都可以使用inject接收到注入的信息
这里我们还是用经典爷父孙组件举例
爷组件outside:
<template>
<div>
<Center></Center>
</div>
</template>
<script>
import Center from "./center.vue";
export default {
components: {
Center,
},
provide: {
foo: "来自outside的foo的值",
},
data() {
return {
};
},
};
</script>
<style lang="">
</style>
孙组件inner:
<template>
<div>
这里是inject的foo的值:{{ foo }}
</div>
</template>
<script>
export default {
inject: {
foo: {
default: () =>'inner默认foo的值',//默认值
},
},
};
</script>
<style lang="">
</style>
结果:
在多个父级组件都provide注入依赖时,例如outside、center都注入依赖,我们可以在子组件inner中选择数据的来源
父组件center:
<template>
<div>
<Innner v-bind="$attrs" v-on="$listeners"></Innner>
</div>
</template>
<script>
import Innner from "./innner.vue";
export default {
provide: {
bar: "来自center的foo的值",
},
components: {
Innner,
},
};
</script>
目前父级组件已经注入了 foo和bar两个变量
子组件inner:
<template>
<div>
这里是inject的foo的值:{{ foo }}
</div>
</template>
<script>
export default {
props: {
aa: { type: String, default: "haha" },
bb: { type: String, default: "heihei" },
},
inject: {
//添加defualt使其变为可选项
foo:{
from:'bar',//来源从outside的foo变为center的bar
defualt:'来自inner的foo的值'
}
}
};
</script>
结果:
localStorage
这里没啥好说的,用sessionStorege也可以。
父组件window.localStorage.setItem储存变量
子组件window.localStorage.getItem获得变量
outside:
<template>
<div>
<Center ></Center>
</div>
</template>
<script>
import Center from "./center.vue";
export default {
components: {
Center,
},
data() {
return {
stroregeMsg: "来自outside的Msg",
};
},
mounted() {
window.localStorage.setItem("Msg", this.stroregeMsg);
},
};
</script>
<style lang="">
</style>
inner:
<template>
<div>
这里是inner的msg的值:{{ msg }}
</div>
</template>
<script>
export default {
data() {
return {
msg: "",
};
},
mounted() {
this.msg = window.localStorage.getItem("Msg");
},
};
</script>
结果:
Vuex
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
可以简单理解为一个公用的状态管理中心。推荐vuex官网
vuex文件:
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
vuexMsg: ""
},
mutations: {
getVuexMgs: function (state, val) {
state.vuexMsg = val
}
},
actions: {
},
modules: {
}
})
outside:
<template>
<div>
<Center :aa="a1" :bb="b1" :cc="c1" @onCenter="handleChange"></Center>
</div>
</template>
<script>
import Center from "./center.vue";
export default {
components: {
Center,
},
data() {
return {
vuexMsg: "来自outside的vuexMsg",
};
},
mounted() {
this.$store.commit("getVuexMgs", this.vuexMsg);
},
};
</script>
<style lang="">
</style>
inner:
<template>
<div>
这里是inner的vuexmsg的值:{{ this.$store.state.vuexMsg }}
</div>
</template>
<script>
export default {
data() {
return {
};
},
};
</script>
结果:
结语
在干业务对话框的二次封装时,使用props,$emit封装那个麻烦程度让我备受打击,一度以为自己傻了。于是想有无更加快捷的传值方式。干脆一不做二不休,做一次组件传值的技术总结。