25-题外:组件通信方式

1.props

适用于的场景:父子组件通信

注意事项:

  • 如果父组件给子组件传递数据(函数):本质其实是子组件给父组件传递数据
  • 如果父组件给子组件传递数据(非函数):本质就是父组件给子组件传递数据

书写方式:3种

1
2
3
['todos']
{type:Array}
{type:Array,default:[]}

特殊情况:路由传递props

  1. 布尔值类型,把路由中params参数映射为组件props数据
  2. 对象,静态数据,很少用
  3. 函数,可以把路由中 params|query 参数映射为组件props数据

2.自定义事件

使用于场景:子组件给父组件传递数据

$on与$emit

  • $emit绑定一个自定义事件event,当这个这个语句被执行到的时候,就会将参数arg传递给父组件,父组件通过@event监听并接收参数。

3.全局事件总线 $bus

使用于场景:万能

组件实例的原型的原型指向的 Vue.prototype

Vue.prototype.$bus = this;

对于比较小型的项目,没有必要引入 vuex 的情况下,可以使用 eventBus。

它的实现思想也很好理解,在要相互通信的两个组件中,都引入同一个新的vue实例,然后在两个组件中通过分别调用这个实例的事件触发和监听来实现通信。

4.pubsub-js

适用于场景:万能

vue 当中几乎不用(因为vue中有全局事件总线和这个第三方提供的库功能重复)

React框架中使用比较多(发布与订阅)

5.Vuex

适用于场景:万能

数据非持久化

核心概念:

  • State:保存所有组件的共享状态
  • Getters:类似状态值的计算属性
  • Mutations:修改 State中状态值的唯一方法,里面包含状态变化监听器和记录器
  • Actions:用于异步处理 State中状态值,异步函数结束后调用Mutations
  • Modules:当一个 State 对象比较庞大时,可以将 State 分割成多个Modules 模块。

6.插槽

适用于场景:父子组件通信 — (结构)

  • 默认插槽
  • 具名插槽
  • 作用域插槽:子组件的数据来源于父组件,但是子组件的自己的结构有父亲决定。

插槽就是子组件中的提供给父组件使用的一个占位符,用<slot></slot> 表示,父组件可以在这个占位符中填充任何模板代码,如 HTML、组件等,填充的内容会替换子组件的<slot></slot>标签。

7.小总结

  1. 可以实现任意组件的通信的方法有两个:事件总线 和 Vuex,事件总线难维护数据但轻量,Vux维护数据方便但比较重量。
  2. 可以实现父与子孙跨越层级通信的方法也有两个:$attrs/$listenersprovide/inject$attrs/$listeners 具有响应性且可以双向通信,provide/inject无响应性且只能单向通信(父传子)
  3. 只能实现父与子组件通信的方法有一个:props/emit,方法比较基础,适合只有父子组件通信的方法,若想跨层级通信需要中间组件做转发,比较麻烦。

8.v-model

CustomInput.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<template>
<div style="background: #ccc; height: 50px;">
<h2>input包装组件</h2>
<!--
:value 动态数据v-bind
@input 给原生DOM绑定原生DOM事件
-->
<input type="text" :value="value" @input="$emit('input',$event.target.value)">
</div>
</template>

<script type="text/ecmascript-6">
export default {
name: 'CustomInput',
props:['value']
}
</script>

ModelTest.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<template>
<div>
<!-- 深入学习v-model:实现父子组件数据同步(实现父子组件通信) -->
<hr />
<!--
组件身上的:value是props,父子组件通信
这里的@input是 非原生DOM的input事件 ,属于自定义事件
-->
<CustomInput :value="msg" @input="msg = $event" />
<!--上面那种写法可以简化为下面这种写法,可以实现父子组件数据同步,
v-model不仅可以在表单元素身上使用,还可以在非表单元素身上使用,
后台管理系统中经常在非表单元素身上,或者已经封装好的身上v-model-->
<CustomInput v-model="msg" />
<span>{{ msg }}</span>
</div>
</template>

<script type="text/ecmascript-6">
import CustomInput from './CustomInput.vue'
export default {
name: 'ModelTest',
components: {
CustomInput
},
data() {
return {
msg: '我爱你中国'
}
},

}
</script>

image-20220720160842780

9.属性修饰符.sync

9.1 基础结构

SyncTest.vue

image-20220720161216737

效果:

image-20220720161239089

9.2 不使用sync修饰符

SyncTest.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<template>
<div>
小明的爸爸现在有{{money}}元
<h2>不使用sync修改符</h2>
<!--
:money 父组件给子组件传递props
@update:money给子组件绑定的自定义事件,只不过名字叫做update:money
目前这种操作,其实和v-model很相似,可以实现父子组件数据同步
money=$event 给monet重新赋值,为子组件传回来的数据
-->
<Child :money="money" @update:money="money = $event" />
<h2>使用sync修改符</h2>
<hr>
</div>
</template>

<script type="text/ecmascript-6">
import Child from './Child.vue'
export default {
name: 'SyncTest',
data() {
return {
money: 10000
}
},
components: {
Child,
},
}
</script>

Child.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<template>
<div style="background: #ccc; height: 50px;">
<span>小明每次花100元</span>
<!-- $emit触发自定义事件,第一个参数是触发自定义事件的名字,
第二个参数是子组件需要把父亲还剩多少钱告诉父亲 -->
<button @click="$emit('update:money',money-100)">花钱</button>
爸爸还剩{{money}}元
</div>
</template>

<script type="text/ecmascript-6">
export default {
name: 'Child',
props:['money']
}
</script>

效果:

image-20220720161712884

image-20220720161627872

9.3 使用sync修饰符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<template>
<div>
小明的爸爸现在有{{money}}元
<h2>使用sync修改符</h2>
<!--
:money.sync的作用:
第一,父组件给子组件传递props money
第二,给当前子组件绑定了自定义事件,而且事件名称即为update:money
-->
<Child :money.sync="money" />
<hr>
</div>
</template>

<script type="text/ecmascript-6">
import Child from './Child.vue'
export default {
name: 'SyncTest',
data() {
return {
money: 10000
}
},
components: {
Child,
},
}
</script>

:money.sync,代表父组件给子组件传递props[money],给当前子组件绑定一个自定义事件(update:money)

10. $attrs和$listeners

10.1 封装ElementUI按钮,并且是带Hover提示的按钮

新建HintButton,里面放一个index.vue,这个文件用来封装按钮

image-20220720162833922

AttrsListenersTest.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<template>
<div>
<h2>自定义带Hover提示的按钮</h2>
<!-- 当用户再使用我们封装的按钮的时候,需要向HintButton组件传递相应的参数 (我们这里做的就是el-button二次封装) -->
<HintButton type="success" icon="el-icon-delete" size="mini" title="提示按钮"
@click="handler"></HintButton>
</div>
</template>

<script >
import HintButton from './HintButton/index.vue';
export default {
name: "AttrsListenersTest",
methods: {
//点击事件的回调
handler() {
alert(666)
}
},
components: { HintButton }
}
</script>

HintButton

这回我们子组件接收父组件传递过来的参数,我们就不用props了,我们利用组件实例身上的一个属性$attrs也可以接收父组件传递给子组件的数据

在子组件中打印一下$attrs

image-20220720163113596

我们注释props:['title'],利用$attrs接收参数的时候,会发现参数全部接收到了

image-20220720163132358

我们打开props:['title'],利用$attrs接收参数的时候,会发现参数除了props接收的title,其他参数都被$attrs接收了

image-20220720163223688

所以,这里可以总结为:对于子组件而言,父组件给的数据可以利用props接收,注意,如果子组件通过props接收的属性,在$attrs属性当中是获取不到的

因为我们需要封装ElementUI按钮,并且是带Hover提示的按钮

HintButton/index.vue中:

我们在el-button外面来一个a标签,因为a标签有一个title属性,就带有提示功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<template>
<div>
<!-- 可以巧妙利用a标签实现按钮带有提示功能 -->
<a :title="title">
<!--
这是复杂的写法,也可以实现效果,但是当属性过多的时候就很麻烦了
<el-button :type="$attrs.type" :icon="$attrs.icon" :size="$attrs.size"></el-button>
-->
<!-- 下面这种写法v-bind不能用 : 不然没效果,这是简单的写法实现效果 -->
<!-- v-on也不能用 @ 不然没效果-->
<el-button v-bind="$attrs" v-on="$listeners"></el-button>
</a>
</div>
</template>

<script>
export default {
props:['title'],
mounted () {
//$attrs属于组件实例自身的一个属性,可以获取到父组件传递过来的props数据
//对于子组件而言,父组件给的数据可以利用props接收,
//注意,如果子组件通过props接收的属性,在$attrs属性当中是获取不到的
console.log(this.$attrs);
//$listeners,它也是组件实例自身的一个属性,它可以获取到父组件给子组件传递的自定义事件
console.log(this.$listeners);
},
}
</script>
<style lang="scss" scoped>
</style>

image-20220720163348444

10.2 $listeners获取父组件给子组件传递的自定义事件

这个时候我们想给按钮绑定一个点击事件,点击弹出一个666什么的,我们知道HintButton是一个组件,在组件身上写@click他就变成了自定义事件了

@click.native其实是可以解决的,但是我们这里有另外的解决办法,所以我这样来演示:

父组件绑定自定义事件

image-20220720163615015

我们想给HintButton绑定单击事件 实际上是给el-button绑定单击事件

我们先看看$listeners身上有没有父组件传递过来的自定义事件,并且利用v-on来给el-button绑定单击事件

HintButton/index.vue中:

子组件中

image-20220720163647822

打印发现:$listeners确实可以获取父组件给子组件传递的自定义事件

image-20220720163745480

此时我们再点击一个按钮,会发现成功弹出了666

image-20220720163812048

10.3 总结

$listeners与$attrs(组件通信方式的一种)

他们两者都是组件实例的属性,可以获取到父组件给子组件传递的props与自定义事件


25-题外:组件通信方式
https://flepeng.github.io/021-frontend-04-Vue-01-course-25-题外:组件通信方式/
作者
Lepeng
发布于
2023年8月10日
许可协议