【Vue全家桶】组件系列(二)—组件通信(props、$emit、事件总线、Provide、Inject)
文章目录
前言
在开发过程中,我们会经常遇到需要组件之间相互进行通信:
- 比如App可能使用了多个Header,每个地方的Header展示的内容不同,那么我们就需要使用者传递给Header一些数据,让 其进行展示;
- 又比如我们在Main中一次性请求了Banner数据和ProductList数据,那么就需要传递给它们来进行展示;
- 也可能是子组件中发生了事件,需要由父组件来完成某些操作,那就需要子组件向父组件传递事件;
总之,在一个Vue项目中,组件之间的通信是非常重要的环节,所以接下来我们就具体学习一下组件之间是如何相互之间传递数据的
一、props / $emit
父子组件之间如何进行通信呢?
- 父组件传递给子组件:通过props属性;
- 子组件传递给父组件:通过$emit触发事件
1.1 父组件传递给子组件
在开发中很常见的就是父子组件之间通信,比如父组件有一些数据,需要子组件来进行展示:
- 这个时候我们可以通过props来完成组件之间的通信;
什么是Props呢?
- Props是你可以在组件上注册一些自定义的attribute;
- 父组件给这些attribute赋值,子组件通过attribute的名称获取到对应的值;
Props的常见用法:
- 使用字符串数组来声明 props,数组中的字符串就是attribute的名称;
export default {
props: ['foo'],
created() {
console.log(this.foo)
}
}
- 使用对象的形式声明props,对象类型我们可以在指定attribute名称的同时,指定它需要传递的类型、是否是必须的、默认值等等;
- 校验选项中的
type
可以是下列这些原生构造函数:String
Number
Boolean
Array
Object
Date
Function
Symbol
- 另外,
type
也可以是自定义的类或构造函数,Vue 将会通过instanceof
来检查类型是否匹配。 default
:为该 prop 指定一个当其没有被传入或值为undefined
时的默认值。对象或数组的默认值必须从一个工厂函数返回。工厂函数也接收原始 prop 对象作为参数。required
:定义该 prop 是否必需传入。在非生产环境中,如果 required 值为真值且 prop 未被传入,一个控制台警告将会被抛出。
- 校验选项中的
export default {
props: {
titles: {
type: Array,
// 对象或者数组应当用工厂函数返回。
// 工厂函数会收到组件所接收的原始 props 作为参数
default: () => []
}
}
}
Prop 名字格式
- 如果一个 prop 的名字很长,应使用 camelCase 形式,可以直接在模板的表达式中使用,也可以避免在作为属性 key 名时必须加上引号。
- 你也可以在向子组件传递 props 时使用 camelCase 形式,但实际上为了和 HTML attribute 对齐,我们通常会将其写为 kebab-case 形式
<MyComponent greeting-message="hello" />
对于组件名我们推荐使用 PascalCase,因为这提高了模板的可读性,能帮助我们区分 Vue 组件和原生 HTML 元素。然而对于传递 props 来说,使用 camelCase 并没有太多优势,因此我们推荐更贴近 HTML 的书写风格。
我们可以使用 v-bind
或缩写 :
来进行动态绑定的 props:
<!-- 根据一个变量的值动态传入 -->
<BlogPost :title="post.title" />
总结
-
props
只能是父组件向子组件进行传值,props
使得父子组件之间形成了一个单向下行绑定。子组件的数据会随着父组件不断更新。 -
props
可以显示定义一个或一个以上的数据,对于接收的数据,可以是各种数据类型,同样也可以传递一个函数。 -
props
属性名规则:若在props
中使用驼峰形式,模板中需要使用短横线的形式// 父组件 <template> <div id="father"> <son :msg="msgData" :fn="myFunction"></son> </div> </template> <script> import son from "./son.vue"; export default { name: father, components: { son }, data() { msgData: "父组件数据"; }, methods: { myFunction() { console.log("vue"); } } }; </script>
// 子组件 <template> <div id="son"> <p>{{msg}}</p> <button @click="fn">按钮</button> </div> </template> <script> export default { name: "son", props: ["msg", "fn"] }; </script>
1.2 子组件传递给父组件
子组件需要传递内容到父组件的情况:
- 当子组件有一些事件发生的时候,比如在组件中发生了点击,父组件需要切换内容;
- 子组件有一些内容想要传递给父组件的时候;
子组件向父组件传值:
- 首先,我们需要在子组件中定义好在某些情况下触发的事件名称;
- 其次,在父组件中以v-on的方式传入要监听的事件名称,并且绑定到对应的方法中;
- 最后,在子组件中发生某个事件的时候,根据事件名称触发对应的事件;
声明触发的事件
组件可以显式地通过 emits
选项来声明它要触发的事件:
export default {
emits: ['inFocus', 'submit']
}
总结
$emit
绑定一个自定义事件,当这个事件被执行的时就会将参数传递给父组件,而父组件通过v-on
监听并接收参数。
// 父组件
<template>
<div>
<h2>当前计数:{{ counter }}</h2>
<counter-operation @addOne="add" @subOne="sub"></counter-operation>
</div>
</template>
<script>
import CounterOperation from "./components/CounterOperation.vue"
export default {
components: {
CounterOperation
},
data() {
return {
counter: 0
}
},
methods: {
add(count) {
this.counter++
console.log(count)
},
sub(count) {
this.counter--
console.log(count)
}
}
}
</script>
<!-- 子组件 -->
<template>
<div>
<button @click="increment(+1)">+1</button>
<button @click="decrement(-1)">-1</button>
</div>
</template>
<script>
export default {
emits: ["addOne", "subOne"],
methods: {
increment(count) {
this.$emit("addOne", count)
},
decrement(count) {
this.$emit("subOne", count)
}
}
}
</script>
二、eventBus事件总线
在之前我们只用过父传子,子传父进行传数据,这时候当组件嵌套比较深或比较复杂的情况,这时候就用到了事件总线 (EventBus)
Vue3从实例中移除了 $on
、$off
和 $once
方法,所以我们如果希望继续使用全局事件总线,要通过第三方的库:
- mitt
- tiny-emitter
我们可以封装一个工具eventBus.js
import mitt from "mitt"
const eventBus = mitt()
export default eventBus
eventBus
事件总线适用于父子组件、非父子组件等之间的通信,使用步骤如下:
- 创建事件中心管理组件之间的通信
- 发送事件 -
emit
- 接收事件 -
on
在addCounter组件中发送事件:
<template>
<div>
<button @click="add">加法</button>
</div>
</template>
<script>
import eventBus from "../utils/event-bus.js"
export default {
data() {
return {
num: 0
}
},
methods: {
add() {
console.log("事件在addcounter中监听")
eventBus.emit("addtion", {
num: this.num++
})
}
}
}
</script>
在App组件中接收事件:
<template>
<div>
<div>求和: {{ count }}</div>
<add-counter></add-counter>
</div>
</template>
<script>
import eventBus from './utils/event-bus'
import addCounter from './components/addCounter.vue'
import AddCounter from './components/addCounter.vue'
export default {
components: {
addCounter
},
data() {
return {
count: 0
}
},
created() {
// 事件监听
eventBus.on("addtion", param => {
console.log("事件在app中监听")
console.log(this.count, param.num);
this.count = this.count + param.num
})
}
}
</script>
在上述代码中,这就相当于将num
值存贮在了事件总线中,在其他组件中可以直接访问。事件总线就相当于一个桥梁,不用组件通过它来通信。
三、Provide/Inject
- Provide/Inject用于组件之间共享数据:
- 比如有一些深度嵌套的组件,子组件想要获取父组件的部分内容;
- 在这种情况下,如果我们仍然将props沿着组件链逐级传递下去,就会非常的麻烦;
- 对于这种情况下,我们可以使用 Provide 和 Inject :
- 无论层级结构有多深,父组件都可以作为其所有子组件的依赖 提供者;
- 父组件有一个 provide 选项来提供数据(用于提供可以被后代组件注入的值);
- 子组件有一个 inject 选项来开始使用这些数据(用于声明要通过从上层提供方匹配并注入进当前组件的属性);
//父组件
export default {
data() {
return {
msg: 'foo'
}
}
provide() {
return {
msg: this.msg
}
}
}
//后代组件
export default {
inject: ['foo'],
created() {
console.log(this.foo)
}
}