查看原文
其他

【第3118期】原生“跨组件”通信方式

XboxYan 前端早读课 2024-01-30

前言

介绍了在原生 web 开发中实现跨组件通信的几种方式。首先是利用 label 和 for 属性进行关联,通过点击 label 元素来触发与其关联的按钮的点击事件,实现跨组件通信。其次是利用主动触发事件,通过获取目标元素并主动触发其相应事件来实现通信,不限于点击事件。最后是利用 dispatchEvent 和 addEventListener 方法,通过自定义事件来实现跨组件通信,可以在任意组件中触发和监听事件。这些方法都可以在原生 web 开发中灵活应用,根据实际需求选择合适的方式进行跨组件通信。

今日前端早读课文章由 @XboxYan 分享,公号:前端侦探授权。

正文从这开始~~

现在已经是 “组件化” 开发时代了。

相信大家平时在 vue 或者 react 中都碰到过 “跨组件” 通信的需求,通常我们需要将数据放在一个公共的父级上,然后用 context 之类的方式传递下去,或者借用 pinia 这样的开源库去更好的管理这些数据。

不过,大部分项目可能没有那么复杂,可能只有极少部分需要 “跨组件” 通信或者全局传递的,专门去引入一个全局状态管理库还是有一定成本的,不仅仅是性能开销,还有学习成本。

另外,还有一些旧项目,由于前期组件设计未考虑周全,或者由于后期需求迭代,导致需要跨组件通信,此时再引入状态管理库也有很大的改造成本。

框架用久了,可能有些都忘了,原生 web 并没有组件化的概念,整个页面都是开放的,所以,借助原生的一些行为和方式,也能很轻易的解决 “跨组件” 通信问题,下面列举了几个方式,一起看看吧~

一、label for

举个例子,下面是之前做的一个错别字纠错功能,也就是自动检测错别字,然后提供替换和忽略一些功能,如下

现在不用关注是怎么实现的,如果按照组件化拆分,右边的纠错面板肯定是单独的组件

很自然的,关于忽略的相关函数也是写在这个组件,类似于这样

<!--纠错组件-->
<script setup>
const ignore = () => {
// 相关操作
}
</script>
<template>
...
<button @click="ignore">忽略</button>
...
</template>

嗯,很好,组件化很不错,逻辑也很清晰。

过了两天,产品又需要在另外一个地方也要有 “忽略” 功能,当鼠标悬浮的时候,下拉选项中也有忽略选项,并且这两个忽略的功能是完全一样的,如下

再单独实现一遍肯定是不行的,把这个函数抽离出来?但是还有新的问题,整个错别字的列表也是需要更新的,是不是也要把这个列表也独立出来?

no,当然不需要这么麻烦,仅需要一个 label 标签就可以轻松搞定,如下

<!--写作区域的下拉组件-->
<template>
...
<ui-dropdown>
<label for="AAA">忽略</label>
...
</ui-dropdown>
...
</template>

当然,右边的忽略按钮也需要加上一个相同的 ID(实际中是动态生成的)

<!--纠错组件-->
<template>
...
<button id="AAA" @click="ignore">忽略</button>
...
</template>

这样,通过 label+for 就将这两个元素关联起来了,点击这个 label 元素就相当于点击了右边的忽略按钮,无需任何跨组件通信,是不是非常方便呢?

二、主动触发事件

有时候,需要通信的事件可能并不是通过按钮点击的,比如这样一个写作页面,按照页面布局和功能,各个部分肯定是单独的组件,如下

其中,右上角有一个 “保存” 按钮,可以保存内容,假设是这样

<!--功能组件-->
<script setup>
const save = () => {
// 相关操作
}
</script>
<template>
...
<button @click="save">保存</button>
...
</template>

现在,产品又提出一个要求,希望在写作时,按 Ctrl + S 也能保存内容。

那么,你会怎么处理呢?把保存方法封装一下?全局通信?

其实,我们要做的事情很简单,只需要主动去触发一下保存按钮的点击事件就可以了,当然需要获取到这个按钮,所以要加个 ID

<!--功能组件-->
<template>
...
<button id="saveBtn" @click="save">保存</button>
...
</template>

然后在写作组件中直接通过触发 click 事件就好了

<!--写作组件-->
<script setup>
// Ctrl+S 保存
const save = () => {
document.getElementById('saveBtn').click()
}
</script>

当然,我更习惯于用 dispatch 的方式来触发

document.getElementById('saveBtn').dispatchEvent(new Event('click'))

这样的好处是不限类型,任意事件都可以触发,而不仅仅是点击事件

dom.dispatchEvent(new Event('mouseover'))

关于 dispatchEvent,下面还有更灵活的运用

三、dispatchEvent 和 addEventListener

dispatchEvent 不仅可以触发常见的点击事件,也能够触发任意自定义事件。

举个例子,有两个相互独立的功能区,有两个按钮,分别点击后,会做一些操作,比如请求接口,最后会弹出同一个奖励弹窗

如果按照组件话的思维来考虑,可能需要将奖励弹窗的状态放在一个公共的父级,然后依次回调,或者用全局状态管理库区管理这些状态。

但是,如果从原生 web 来考虑,其实不必那么麻烦,下面是一些伪代码

<!--功能组件A-->
<script setup>
const getPrize = () => {
// 领奖励一系列逻辑
}
</script>
<template>
...
<button @click="getPrize">领奖励A</button>
...
</template>

然后还有功能区 B

<!--功能组件B-->
<script setup>
const getPrize = () => {
// 领奖励B一系列逻辑
}
</script>
<template>
...
<button @click="getPrize">领奖励B</button>
...
</template>

然后还有奖励弹窗

<!--奖励弹窗-->
<script>
const open = () => {
// 打开弹窗
}
</script>
<template>
...
<dialog>奖励弹窗</dialog>
...
</template>

那么,怎么让其他功能区的组件打开奖励弹窗呢?

这就需要用自定义事件了,很简单,直接触发一个自定义事件,假设就叫做 prize

document.dispatchEvent(new CustomEvent('prize'))

放在业务组件中就是

<!--功能组件A-->
<script setup>
const getPrize = () => {
// 领奖励一系列逻辑
document.dispatchEvent(new CustomEvent('prize'))
}
</script>
<template>
...
<button @click="getPrize">领奖励A</button>
...
</template>

然后,我们可以在页面任意地方都监听到这个事件,直接通过 addEventListener 就可以了

document.addEventListener('prize', () => {
// 监听到了prize
})

所以,我们可以把这个监听直接放在奖励弹窗组件中

<!--奖励弹窗-->
<script>
const open = () => {
// 打开弹窗
}

onMounted(() => {
document.addEventListener('prize', () => {
// 监听到了prize,打开弹窗
open()
})
})
</script>
<template>
...
<dialog>奖励弹窗</dialog>
...
</template>

这样,无论组件是什么样的嵌套关系,都可以随心所欲地通信了

四、最后总结一下

本文更多的还是提供一种思路,实际开发中可以自己权衡,什么方式比较合适,下面总结一下这三种方式

  • label for 比较适合于按钮点击事件的复用,可以将 label 元素与 button 元素绑定起来

  • 相比 label for 而言,主动触发事件更灵活,可以触发任意事件,无需 button 元素,也不限制点击事件

  • dispatchEvent 和 addEventListener 是最灵活的方式了,几乎可以做到全局通信,通过 dispatchEvent 触发,然后通过 addEventListener 监听

关于本文
作者:@XboxYan
原文:https://mp.weixin.qq.com/s/xNgTM1OCh4djQjfC0dYGjA

这期前端早读课
对你有帮助,帮” 
 “一下,
期待下一期,帮”
 在看” 一下 。

继续滑动看下一个

【第3118期】原生“跨组件”通信方式

向上滑动看下一个

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存