「这是我参与2022首次更文挑战的第11天,活动详情查看:2022首次更文挑战」。
前言
首先,我们要明白,vue组件通信主要分为:
- 父子组件之间通信(又具体分为父组件向子组件传值、子组件向父组件传值) :
props、$emit、$refs、.sync、v-model、$children和$parent - 父组件跟孙子组件通信:
$arrts和$listeners、provide和inject - 非父子组件之间的通信:
eventbus、vuex
一、父子组件之间通信
1. props(父组件=>子组件)
// 父组件 parent.vue
<template>
<div>
<child :title="title" :list="list"></child>
</div>
</template>
<script>
import child from '@/demo/child'
export default {
components: {
child
},
data () {
return {
title: '事项',
list: ['吃早饭', '刷剧', '码代码']
};
},
};
</script>
// 子组件 child.vue
<template>
<div>
<p>{{title}}</p>
<ul>
<li v-for="item in list" :key="item">{{item}}</li>
</ul>
</div>
</template>
<script>
export default {
props: {
title: {
type: String, // props校验
default: '标题' // 默认值
},
list: {
type: Array,
default: () => ['吃饭', '睡觉', '打豆豆']
}
},
};
</script>
传值和未传值效果图
- props 的 type 值为:
String、Number、Boolean、Array、Object、Date、Function、Symbol- 注意:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外变更父级组件的状态,从而导致你的应用的数据流向难以理解。
- 每次父级组件发生变更时,子组件中所有的 prop 都将会刷新为最新的值。这意味着你不应该在一个子组件内部改变 prop。如果你这样做了,Vue 会在浏览器的控制台中发出警告
- 简而言之:
prop是单向下行绑定的.
2.$emit (子=>父)
// 子组件 child.vue
<template>
<div>
<p>{{title}}</p>
<ul>
<li v-for="item in list" :key="item" @click="clickItem(item)">{{item}}</li>
</ul>
</div>
</template>
<script>
export default {
data () {
return {
};
},
props: {
title: {
type: String, // props校验
default: '标题' // 默认值
},
list: {
type: Array,
default: () => ['吃饭', '睡觉', '打豆豆']
}
},
methods: {
fn () {
console.log('9999999999');
},
clickItem (item) {
// 传多个 传方法
// this.$emit('clickItem', [item, this.fn])
this.$emit('clickItem', item)
}
},
};
</script>
// 父组件 parent.vue
<template>
<div>
<child @clickItem="clickItem"></child>
<p>点击了: {{item}}</p>
</div>
</template>
<script>
import child from '@/demo/child'
export default {
components: {
child
},
data () {
return {
title: '事项',
list: ['吃早饭', '刷剧', '码代码'],
item: ''
};
},
methods: {
clickItem (item) {
this.item = item
}
},
};
</script>
效果图
子组件
$emit注册事件 第一个参数是事件名字,父组件要保持一致,多个传值,传一个数组,可以传递方法
3. .sync (父子双向通信)
在有些情况下,我们可能需要对一个 prop 进行“双向绑定”。这时候 .sync 就上场了。.sync 修饰符是作为一个编译时的语法糖存在。它会被扩展为一个自动更新父组件属性的 v-on 监听器
<child :isShow.sync="showValue"></child> // 是语法糖,最后会被解析成
<child :isShow="showValue" @update:isShow="val => showValue = val"></child>
然后子组件就这样向父组件传值即可
this.$emit('update:isShow', newValue)
// isShow是要修改的键 newVaule是值
代码示例
// 父组件 parent.vue
<template>
<div>
<p>这是一行标题</p>
<child :isShow.sync="isShow"></child>
</div>
</template>
<script>
import child from '@/demo/child'
export default {
components: {
child
},
data () {
return {
isShow: true
};
},
};
</script>
// 子组件 child.vue
<template>
<div>
<el-button @click="toggleShow">显示/隐藏详情</el-button>
<p v-if="isShow">这是一段详情。这是一段详情。这是一段详情。</p>
</div>
</template>
<script>
export default {
props: {
isShow: {
type: Boolean, // props校验
}
},
methods: {
toggleShow () {
// 直接修改props的值会报错 通过触发父组件.sync的update方法传值并修改
// 实现父传值给子 子修改并赋值
this.$emit('update:isShow', !this.isShow)
}
},
};
</script>
4. v-model(父子双向通信)
v-model可以在自定义组件上使用,可以实现双向通信。v-model其实也是个语法糖。比如说,
<input v-model="something">// 是语法糖,最后会被解析成 <input :value="something" @input="something = $event.target.value">
// 父组件 parent.vue
<template>
<div>
<p>这是一行标题</p>
<child v-model="isShow"></child>
</div>
</template>
<script>
import child from '@/demo/child'
export default {
components: {
child
},
data () {
return {
isShow: true
};
},
};
</script>
// 子组件 child.vue
<template>
<div>
<button @click="toggleShow">显示/隐藏详情</button>
<p v-if="value">这是一段详情。这是一段详情。这是一段详情。</p>
</div>
</template>
<script>
export default {
data () {
return {
};
},
props: ['value'], //接收一个 value prop,注意,这里用的是value
methods: {
toggleShow () {
// 直接修改props的值会报错 通过触发父组件v-model的input方法传值并修改
// 实现父传值给子 子修改并赋值
this.$emit('input', !this.value)
}
},
};
</script>
5. ref(父调用子的方法或访问子的数据)
ref:如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果在子组件上引用,就指向组件实例,我们可以通过实例直接调用组件的方法或访问数据, 下面举个例子,如何通过父组件获取子组件的值 以及 控制子组件的值+1
// 父组件 parent.vue
<template>
<div>
<child ref="child"></child>
<button @click="clickBtn">点击+1</button>
</div>
</template>
<script>
import child from '@/demo/child'
export default {
components: {
child
},
methods: {
clickBtn () {
console.log(this.$refs.child); // 拿到组件实例
// this.$refs.child.num = 10 // 直接修改
this.$refs.child.changeNum() // 调子方法
}
},
};
</script>
// 子组件 child.vue
<template>
<div>
<p>{{num}}</p>
</div>
</template>
<script>
export default {
data () {
return {
num: 0
};
},
methods: {
changeNum () {
this.num++
}
},
};
</script>
6.$children和$parent
// 父组件 parent.vue
<template>
<div>
<child></child>
<button @click="clickBtn">点击+1</button>
</div>
</template>
<script>
import child from '@/demo/child'
export default {
components: {
child
},
data () {
return {
msg: '父组件的信息'
}
},
methods: {
clickBtn () {
this.$children[0].changeNum() // 调子方法 $children返回的是一个数组
}
},
};
</script>
// 子组件 child.vue
<template>
<div>
<p>{{num}}</p>
<p>{{msg}}</p>
</div>
</template>
<script>
export default {
data () {
return {
num: 0
};
},
computed: {
msg () {
return this.$parent.msg // $parent是一个对象
}
},
methods: {
changeNum () {
this.num++
}
},
};
</script>
注意:
this.$parent和this.$children返回的值不一样,this.$children的值是数组,而this.$parent是个对象- 在#app上的
this.$parent得到的是new Vue()的实例,在这实例上再this.$parent得到的是undefined,而在最底层的子组件的this.$children是个空数组
二、父-子-孙组件之间通信
1. $attr 和 $listeners
当我们写高级别的组件的时候,如果有N个props以及N个$emit触发的事件,可以用$attr和$listeners轻松解决,否则的话每一个从父组件传到子组件的props,我们都得在子组件的 props 中显式的声明才能使用。
这样一来,我们的子组件每次都需要申明一大堆 props. 遇到多级组件嵌套的情况代码会显得非常的冗余,有了$attr和$listeners不用将 props 一层一层往下传递。
$attrs、$listeners 都是可以跨域父子组件,可以父 - 子 - 孙组件传递。下面举个例子,用$attrs、$listeners实现父组件parent.vue跟孙子组件grandson.vue的通信。
// 父组件 parent.vue
<template>
<div>
<child :message="message" :number="number" @upNumber="upNumber" @input="upMsg" />
</div>
</template>
<script>
import child from '@/demo/child'
export default {
components: {
child
},
data () {
return {
message: "parent",
number: 0
}
},
methods: {
upNumber (value) {
this.number = value;
},
upMsg (value) {
this.message = value;
},
},
};
</script>
// 子组件 child.vue
<template>
<grandson v-bind="$attrs" v-on="$listeners" />
</template>
<script>
import grandson from "@/demo//grandson";
export default {
inheritAttrs: false,
components: {
grandson
},
};
</script>
// 孙子组件 grandson.vue
<template>
<div class="children" style="font-size:18px">
{{$attrs.message}}
<p @click="$listeners.upNumber($attrs.number + 1)">点击数字实现递增{{$attrs.number}}</p>
</div>
</template>
<script>
export default {
inheritAttrs: false,
mounted () {
console.log(this.$attrs); // 不包含class 和 style 参数data
console.log(this.$listeners); // 方法对象
setTimeout(() => {
// 用$emit跟$listeners都行
// this.$emit("input", "children");
// this.$emit('upNumber', this.$attrs.number + 1)
this.$listeners.input("children")
this.$listeners.upNumber(this.$attrs.number + 1)
}, 1500);
}
};
</script>
注意: $attr包含了父作用域中不作为 prop 被识别 (且获取) 的 attribute 绑定 (class 和 style 除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (class 和 style 除外),并且可以通过 v-bind="$attrs" 传入内部组件——在创建高级别的组件时非常有用。
inheritAttrs
inheritAttrs 默认是true, 如果没设置为false的话,父作用域的不被认作 props 的特性绑定将会“回退”且作为普通的 HTML 特性应用在子组件的根元素上。如果我们设置 inheritAttrs:false,这些默认行为将会被去掉。是不是觉得特别晦涩难懂,别慌~ 看下面对比图,相信你就懂了!
2. provide 和 inject
provide/ inject 是vue2.2.0新增的api, 简单点来说就是父组件中通过provide来提供变量, 然后再子组件中通过inject来注入变量。
provide和inject主要在开发高阶插件/组件库时使用。并不推荐用于普通应用程序代码中。provide和inject允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在其上下游关系成立的时间里始终生效。如果你熟悉 React,这与 React 的上下文特性很相似。
// 父组件 parent.vue
<template>
<div class="parent">
<child />
</div>
</template>
<script>
import child from "@/demo/child";
export default {
components: {
child
},
provide: {
message: 'parent',
fn () {
console.log('999999999999');
}
}
};
</script>
// 子组件 child.vue
<template>
<div>
<Grandson />
<p>{{message}}</p>
</div>
</template>
<script>
import Grandson from "@/demo/grandson";
export default {
components: {
Grandson
},
inject: ['message', 'fn'],
mounted () {
console.log(this.fn);
}
};
</script>
// 孙子组件 grandson.vue
<template>
<div>
{{message}}
</div>
</template>
<script>
export default {
inject: ['message', 'fn'],
mounted () {
console.log(this.fn);
}
};
</script>
三、非父子组件之间通信:
1.eventbus
eventBus又称为事件总线,在vue中可以使用它来作为沟通桥梁, 就像是所有组件共用相同的事件中心,可以向该中心注册发送事件或接收事件, 所以组件都可以通知其他组件。
针对中大型项目, 首选Vuex, 但是如果是小型项目使用Vue的eventBus, 是一个不错的选择。
全局的eventBus简单理解为在一个文件创建一个新的vue实例然后暴露出去, 使用的时候import这个模块进来即可。
我们在来实现comp2.vue向comp1.vue传递数据。做个简单的累加器。
// event-bus.js
import Vue from 'vue'
export const EventBus = new Vue()
// parent.vue
<template>
<div>
<comp1></comp1>
<comp2></comp2>
</div>
</template>
<script>
import comp1 from '@/components/comp1'
import comp2 from '@/components/comp2'
export default {
components: {
comp1,
comp2
}
};
</script>
// comp2.vue 发送事件
<template>
<div>
<button @click="additionHandle">+累加</button>
</div>
</template>
<script>
import { EventBus } from '@/util/event-bus.js'
export default {
data () {
return {
num: 1
};
},
methods: {
fn () {
console.log('999999999999');
},
additionHandle () {
EventBus.$emit('add', {
num: this.num++
})
// EventBus.$emit('add', this.fn) 传函数
// EventBus.$emit('add', 'text') 多参传数组
}
},
};
</script>
// comp1.vue 接收事件
<template>
<div>计算和: {{count}}</div>
</template>
<script>
import { EventBus } from '@/util/event-bus.js'
export default {
data () {
return {
count: ''
};
},
mounted () {
EventBus.$on('add', param => {
this.count = param.num;
// console.log(param);
// this.fn(param)
})
},
methods: {
fn (item) {
console.log(item);
},
},
};
</script>
缺点: 当项目较大, eventBus难以维护
2.vuex
vuex这里就不赘述了,建议直接移步官方文档
总结
下面,再来一张思维导图来阐述通信的主要方式。
写在最后
学习英语好处很多
props 中: 道具
emit 中: 发出 发射
sync 中: 同时,同步;协调,一致
model 中: 模型;模式;
parent 和 children 中: 父亲 儿子
attr 中: 属性(attribute)
listeners 中: 听众;监听器(listener 的复数)
provide 中: 提供,供给;配备,准备好;
inject 中: 注射;(给……)添加,增加(某品质)
eventbus 中: enent 事件 bus 公共汽车
感谢 juejin.cn/post/696380…