时隔一年多 vue 又增加了新的方法 所以又把此篇文章从新整理
2.2.0 新增 provide 和 inject (隔代传值)
2.4.0 新增 $attrs 和 $listeners (隔代传值)
已经 2022 年 vue 已经出了3.0版本,在组件传值方便也有了一些改动,最近有时间来做一些添加和修正
组件是 vue.js 最强大的功能之一,而组件实例的作用域是相互独立的,这就意味着不同组件之间的数据无法相互引用。那么组件间如何通信,也就成为了vue中重点知识了。这篇文章将会通过props、$ref和 $emit 这几个知识点,来讲解如何实现父子组件间通信。
方法1: props
/ $emit
(父子传值)
1.父向子
通过 props 传递数据
parent.vue
<template>
<div class="parent">
父亲:{{message}}
<child :my-message="message" mm="123"></child>
// 注意传递参数时要用—代替驼峰命名,HTML不区分大小写
// 通过属性传递 可传递动态属性 也可以传递静态属性 可传递数字、布尔值、数组、对象等任何类型的值
</div>
</template>
<script>
import child from '../base/child'
export default {
data(){ // 组件的data必须是函数
return {
message:'好好学习'
}
},
components:{
child
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
</style>
child.vue
<template>
<div class="child">
儿子:{{myMessage}}+{{mm}}
</div>
</template>
<script>
export default {
props:['myMessage','mm'],// 方式一
// 方式二
// props: {
// myMessage: Array //指定传入的类型,如果类型不对,会警告
// },
// // 方式三
// props: {
// myMessage: {
// type: Array,
// default: [5,6,7] //指定默认的值
// }
// }
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
</style>
2. 子向父
通过 $emit 向父组件派发事件,父组件调用获取子组件参数
parent.vue
<template>
<div class="parent">
<child @tell='know'></child>// ref 作用在组件上 指向的是组件的实例 实例上的方法都可以调用
</div>
</template>
<script>
import child from '../base/child'
export default {
data(){ // 组件的data必须是函数
return {
}
},
methods:{
know(msg) {
alert(msg);
}
},
components:{
child
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
</style>
VUE 2 写法
child.vue
<template>
<div class="child" @click='childClick'>
</div>
</template>
<script>
export default {
data() {
return {
msg: '我是子组件传递过来的数据'
}
},
methods:{
childClick(){
this.$emit('tell',this.msg) // 向父组件派发事件,this.msg为向父组件传递的数据
}
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
.child{
width: 100px;
height: 100px;
background-color: blue;
}
</style>
VUE 3 写法
child.vue
<template>
<div class="child" @click='childClick'>
</div>
</template>
<script>
export default {
emits: ['tell'], // vue 3需要对可触发的事件进行注册
data() {
return {
msg: '我是子组件传递过来的数据'
}
},
methods:{
childClick(){
this.$emit('tell',this.msg) // 向父组件派发事件,this.msg为向父组件传递的数据
}
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
.child{
width: 100px;
height: 100px;
background-color: blue;
}
</style>
方法2: $refs
/ $emit
(父子调用方法)
1.父向子
通过 $refs 获取子组件数据 调用子组件方法
parent.vue
<template>
<div class="parent">
<div class="todo" @click='todo'></div>
<child ref='child'></child>// ref 作用在组件上 指向的是组件的实例 实例上的方法都可以调用
</div>
</template>
<script>
import child from '../base/child'
export default {
data(){ // 组件的data必须是函数
return {
}
},
methods:{
todo(){
console.log(this.$refs.child.msg)
this.$refs.child.do()
}
},
components:{
child
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
.todo{
width: 100px;
height: 100px;
background-color: red;
}
</style>
child.vue
<template>
<div class="child">
</div>
</template>
<script>
export default {
data() {
return {
msg: '我是子组件传递过来的数据'
}
},
methods:{
do(){
alert('我是子组件的方法')
}
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
</style>
2.子向父
通过 $emit 向父组件触发一个事件 子组件就可以调用父组件的方法
parent.vue
<template>
<div class="parent">
<child @tell='fatherMethod'></child>// 父组件中 在子组件上监听子组件派发的tell方法 然后调用函数 就能调用子组件的方法
</div>
</template>
<script>
import child from '../base/child'
export default {
data(){ // 组件的data必须是函数
return {
}
},
methods:{
fatherMethod() {
alert('我是父组件的know方法');
}
},
components:{
child
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
</style>
child.vue
<template>
<div class="child" @click='childClick'>
</div>
</template>
<script>
export default {
data() {
return {
msg: '我是子组件传递过来的数据'
}
},
methods:{
childClick(){
this.$emit('tell') // 向父组件派发事件
}
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
.child{
width: 100px;
height: 100px;
background-color: blue;
}
</style>
3.子向父
父组件把方法传入子组件中 在子组件里直接调用这个方法
parent.vue
<template>
<div class="parent">
<child :fatherMethod='fatherMethod'></child>// 父组件把方法传入子组件中,在子组件里直接调用这个方法
</div>
</template>
<script>
import child from '../base/child'
export default {
methods:{
fatherMethod() {
alert('我是父组件的know方法');
}
},
components:{
child
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
</style>
child.vue
<template>
<div class="child" @click='childClick'>
</div>
</template>
<script>
export default {
props:{
fatherMethod: {
type: Function,
default: null
}
},
methods:{
childClick(){
this.fatherMethod()
}
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
.child{
width: 100px;
height: 100px;
background-color: blue;
}
</style>
方法3: $children
/ $parent
(父子传值、调方法)
1.父向子
通过 $children 获取子组件数据和调用子组件方法(VUE 3已经移除,用 $refs 代替)
parent.vue
<template>
<div class="parent" @click='fatherMethod'>
<child></child>
</div>
</template>
<script>
import child from '../base/child'
export default {
methods:{
fatherMethod() {
this.$children[0].childMethod() // $children获取的是子组件的数组 通过索引找到对应的子组件的实例
console.log(this.$children[0].msg)
}
},
components:{
child
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
.parent{
width: 100px;
height: 100px;
background-color: blue;
}
</style>
child.vue
<template>
<div class="child" @click='childClick'>
</div>
</template>
<script>
export default {
data() {
return {
msg: '我是子组件传递过来的数据'
}
},
methods:{
childMethod(){
alert('我是子组件的方法')
}
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
.child{
width: 100px;
height: 100px;
background-color: blue;
}
</style>
2.子向父
通过 $parent 获取父组件数据和调用父组件方法
$root
和 $parent
都能够实现访问父组件的属性和方法,两者的区别在于,如果存在多级子组件,通过parent 访问得到的是它最近一级的父组件,通过root 访问得到的是根父组件(App.vue) 所以存在组件嵌套的情况下 不要使用 $root
parent.vue
<template>
<div class="parent">
<child></child>// ref 作用在组件上 指向的是组件的实例 实例上的方法都可以调用
</div>
</template>
<script>
import child from '../base/child'
export default {
data() { // 组件的data必须是函数
return {
msg:'父组件数据测试'
}
},
methods:{
fatherMethod() {
alert('我是父组件的方法')
}
},
components:{
child
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
</style>
child.vue
<template>
<div class="child" @click='childClick'>
</div>
</template>
<script>
export default {
data() {
return {
msg: '我是子组件传递过来的数据'
}
},
methods:{
childClick(){
this.$parent.fatherMethod()
console.log(this.$parent.msg)
}
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
.child{
width: 100px;
height: 100px;
background-color: blue;
}
</style>
方法4: eventBus
(父子 兄弟 跨级)(VUE3 不支持)
VUE 2
父子 兄弟 跨级
这种方法通过一个空的 Vue 实例
作为中央事件总线(事件中心),用它来触发事件和监听事件,巧妙而轻量地实现了任何组件间的通信,包括父子、兄弟、跨级。当我们的项目比较大时,可以选择更好的状态管理解决方案 vuex。
eventBus.js
import Vue from 'vue';
export default new Vue();
a.vue
<template>
<div>
<span>{{a}}</span>
<input type="button" value="把A数据传给C" @click = "send">
</div>
</template>
<script>
import eventBus from './eventBus';
export default {
data(){
return {
a: "我是a组件中数据"
}
},
methods: {
send () {
eventBus.$emit("a-msg", this.a);
}
}
}
</script>
b.vue
<template>
<div>
<span>{{b}}</span>
<input type="button" value="把B数据传给C" @click = "send">
</div>
</template>
<script>
import eventBus from './eventBus';
export default {
data(){
return {
b: "我是b组件中数据"
}
},
methods: {
send () {
eventBus.$emit("b-msg", this.b);
}
}
}
</script>
c.vue
<template>
<div>
<h3>我是C组件</h3>
<span>接收过来A的数据为: {{a}}</span>
<br>
<span>接收过来B的数据为: {{b}}</span>
</div>
</template>
<script>
import eventBus from './eventBus';
export default {
data(){
return {
a: "",
b: ""
}
},
mounted () {
//接收A组件的数据
eventBus.$on("a-msg", function (a) {
this.a = a;
}.bind(this));
//接收B组件的数据
eventBus.$on("b-msg", function (b) {
this.b = b;
}.bind(this));
}
}
</script>
VUE 3
VUE 3 推荐使用一些第三方库 mitt 或 tiny-emitter
mitt 使用举例:
// eventBus.js
import mitt from 'mitt';
const emitter = mitt();
export default emitter;
// a.js
import emitter from './utils/eventBus'
emitter.emit('panda', {name: 'lokka', age: 2})
emitter.emit('flamingo', {name: 'disy', age: 1})
// b.js
import emitter from './utils/eventBus'
// 逐个监听
—————————————————————————————————————————
emitter.on('panda', (val) => {
console.log(val)
})
emitter.on('flamingo', (val) => {
console.log(val)
})
// 取消监听
funtion onFoo() {}
emitter.on('foo', onFoo)
emitter.off('foo', onFoo)
—————————————————————————————————————————
// 监听多个
emitter.on('*', (type, val) => {
console.log(type, val)
})
// 取消监听多个
emitter.all.clear()
方法5: provide
/ inject
(跨级 依赖注入)
1. 跨级
Vue2.2.0 新增 API,这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。一言而蔽之:祖先组件中通过 provider 来提供变量,然后在子孙组件中通过 inject 来注入变量。
provide / inject API 主要解决了跨级组件间的通信问题,不过它的使用场景,主要是子组件获取上级组件的状态,跨级组件间建立了一种主动提供与依赖注入的关系。
provide
provide是父级组件需要传递给子孙组件的属性/方法。选项有以下几种:
- 一个对象。
- 返回一个对象的函数。
//选项一
provide: {
content: 'hello world'
}
//选项二
provide(){
return {
content: 'hello world'
}
}
inject
inject是子孙组件用来接收父级组件属性/方法。选项有以下几种:
- 一个字符串数组。
- 一个对象,对象的key是本地的绑定名,value是provide定义的名字。
- 一个对象,区分上一种方式是为了设置默认值。对象的key是本地的绑定名,value也是一个对象。该对象有两个键,一个是from(provide定义的名字),另一个是default(默认值)。
//选项一
inject: ['content']
//选项二
inject: {
content: 'content'
}
//选项三
inject: {
content: {
from:'content',
default:'hello world'
}
}
示例:
a.vue
<template>
<div>
<span>{{a}}</span>
<aa></aa>
</div>
</template>
<script>
import aa from './aa';
export default {
data(){
return {
a: "我是a组件中数据"
}
},
provide() {
return {
msg: this.a
}
},
components: {
aa
}
}
</script>
aa.vue
<template>
<div>
</div>
</template>
<script>
export default {
inject: ['msg'],
created() {
console.log(this.msg); // 我是a组件中数据
}
}
</script>
方法6: $attrs
/ $listeners
(跨级)
1. 跨级
多级组件嵌套需要传递数据时,通常使用的方法是通过 vuex。但如果仅仅是传递数据,而不做中间处理,使用 vuex 处理,未免有点大材小用。为此 Vue2.4 版本提供了另一种方法—— $attrs
/ $listeners
- $attrs:包含了父作用域中不被 prop 所识别 (且获取) 的特性绑定 (class 和 style 除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (class 和 style 除外),并且可以通过 v-bind=“$attrs” 传入内部组件。通常配合 interitAttrs 选项一起使用。
- $listeners:包含了父作用域中的 (不含 .native修饰器的) v-on 事件监听器。它可以通过 v-on=“$listeners” 传入内部组件
在vue2.4中,为了解决该需求,引入了$attrs 和 $listeners , 新增了 inheritAttrs 选项。 在版本2.4以前,默认情况下父作用域的不被认作props的属性属性百年孤独,将会“回退”且作为普通的HTML特性应用在子组件的根元素上。如下列的例子
父组件 a.vue:
<template>
<div>
<span>{{name}}</span>
<span>{{price}}</span>
<aa
:name="name"
:price="price"
></aa>
</div>
</template>
<script>
import aa from './aa';
export default {
data(){
return {
name: "可乐",
price: 3
}
},
provide() {
return {
msg: this.a
}
},
components: {
aa
}
}
</script>
子组件 aa.vue:
<template>
<div>
<span>{{name}}</span>
</div>
</template>
<script>
export default {
props: ['name']
}
</script>
可以看到子组件没有props接收的属性就会被放在子组件的根元素上
在2.4中新增选项inheritAttrs
inheritAttrs 的默认值为 true, 如果你不希望没有props接收的属性就放在子组件的根元素上,你可以在组件的选项中设置 inheritAttrs: false。
配合 v-bind=‘$attrs’ 可以使子组件的根元素上的属性传入内部组件
示例:
父组件 a.vue:
<template>
<div>
<span>{{name}}</span>
<span>{{price}}</span>
<aa
:name="name"
:price="price"
:size="size"
></aa>
</div>
</template>
<script>
import aa from './aa';
export default {
inheritAttrs: fasle,
data(){
return {
name: "可乐",
price: 3,
size: "500ml"
}
},
provide() {
return {
msg: this.a
}
},
components: {
aa
}
}
</script>
子组件 aa.vue:
<template>
<div>
<span>{{name}}</span>
<aaa v-bind="$attrs"></aaa>
</div>
</template>
<script>
import aaa from './aaa';
export default {
props: ['name'],
components: {
aaa
}
}
</script>
子组件 aaa.vue:
<template>
<div>
</div>
</template>
<script>
export default {
created() {
console.log(this.$attrs); // {price: 3, size: "500ml"}
}
}
</script>
简单来说:$attrs 与 $listeners 是两个对象,$attrs 里存放的是父组件中绑定的非 Props 属性,$listeners里存放的是父组件中绑定的非原生事件。
$listeners 就不做示例了
方法7: vuex
具体使用方法移步我的文章——vue 知识点整理——状态管理(Vuex)
总结:
常见使用场景可以分为三类:
父子通信:
- 父向子传递数据是通过 props,子向父是通过 $emit
- 通过父链 / 子链也可以通信($parent / $children(
VUE2
)、 $refs(VUE3
)) - $refs 也可以访问组件实例
- provide / inject API
- $attrs / $listeners
兄弟通信:
VUE2
eventBusVUE3
mitt 、tiny-emitter- Vuex
跨级通信:
VUE2
eventBusVUE3
mitt 、tiny-emitter- provide / inject API
- $attrs / $listeners
- Vuex