问题场景
假设在业务场景中,我们需要在弹窗状态变化过程中进行不同的操作。那在这里我们就需要在正确的时机(位置)调用打开弹窗后的方法或者关闭弹窗前的方法,然后再控制弹窗状态。很多情况下,去写关于弹窗的页面的时候往往重复编写这些重复的逻辑,并且在弹窗状态变化的地方直接地编写代码而不进行方法封装。
解决之道
注册确认时(打开时、取消关闭时)的方法,让状态变化时自动进行调用,而非重复地编写类似的逻辑。
注册方法能很好地帮助你将这些方法收集起来,避免重复逻辑代码的编写,同时强制编写者封装方法,也降低了维护的成本。
useConfirmDialog
以下是vueuse提出的一个解决方案。在这里跟大家一起分享交流。其中reveal方法返回的Promise对象是用于异步的使用方式。看完这个部分的代码分析之后,可以参考下方的用法
前置知识
- 文中提到的noop是一个空函数
- createEventHook是一个创建事件的钩子函数。这个函数会返回on、off、trigger方法,分别对应注册方法,取消方法,触发方法。使用on方法传递一个函数作为参数可以让钩子函数中的数组变量收集该函数,而使用trigger方法传入数据,则可以将该数据作为参数,让收集函数的数组去循环调用收集到的函数
useConfirmDialog源代码
import type { ComputedRef, Ref } from 'vue-demi'
import { computed, ref } from 'vue-demi'
import type { EventHook, EventHookOn } from '@vueuse/shared'
import { createEventHook, noop } from '@vueuse/shared'
/**
* Hooks for creating confirm dialogs. Useful for modal windows, popups and logins.
*
* @see https://vueuse.org/useConfirmDialog/
* @param revealed `boolean` `ref` that handles a modal window
*/
export function useConfirmDialog<
RevealData = any,
ConfirmData = any,
CancelData = any,
>(revealed: Ref<boolean> = ref(false)): UseConfirmDialogReturn<RevealData, ConfirmData, CancelData> {
// createEventHook是另一个钩子函数,这里可以先略过,后面我再解释这里的用法
const confirmHook: EventHook = createEventHook<ConfirmData>()
const cancelHook: EventHook = createEventHook<CancelData>()
const revealHook: EventHook = createEventHook<RevealData>()
// 定义并赋值一个空函数,这是为了在confirm方法中调用resolve才定义了这个变量用于在reveal方法中存储resolve
let _resolve: (arg0: UseConfirmDialogRevealResult<ConfirmData, CancelData>) => void = noop
const reveal = (data?: RevealData) => {
// 调用revealHook中注册的方法,调用trigger的时候,其实循环调用了一个数组中存储的方法,并且将
// data作为函数传参进去。
revealHook.trigger(data)
// 修改状态,打开弹窗
revealed.value = true
// 返回一个Promise对象,同时将resolve赋值到先前说的_resolve变量中
return new Promise<UseConfirmDialogRevealResult<ConfirmData, CancelData>>((resolve) => {
_resolve = resolve
})
}
const confirm = (data?: ConfirmData) => {
// 关闭弹窗
revealed.value = false
// 同上,其实也是调用confirmHook注册的方法,同时将data作为参数传入
confirmHook.trigger(data)
// 真正的进行resolve,同时将data和取消状态传出,让外部调用reveal方法的地方可以获取到data
_resolve({ data, isCanceled: false })
}
// 这里的代码类比confirm
const cancel = (data?: CancelData) => {
revealed.value = false
cancelHook.trigger(data)
_resolve({ data, isCanceled: true })
}
return {
// 返回一个计算属性,可以很好地防止外部代码自行修改reveal的状态
isRevealed: computed(() => revealed.value),
reveal,
confirm,
cancel,
// 这里三个Hook的on方法实现是一致的,用于传入一个方法之后注册方法,同时在trigger的时候进行调用。
onReveal: revealHook.on,
onConfirm: confirmHook.on,
onCancel: cancelHook.on,
}
}
官方提倡的用法有两种
- 同步用法
<script setup>
import { useConfirmDialog } from '@vueuse/core'
const {
isRevealed,
reveal,
confirm,
cancel,
onReveal,
onConfirm,
onCancel,
} = useConfirmDialog()
</script>
<template>
<button @click="reveal">Reveal Modal</button>
<teleport to="body">
<div v-if="isRevealed" class="modal-bg">
<div class="modal">
<h2>Confirm?</h2>
<button @click="confirm">Yes</button>
<button @click="cancel">Cancel</button>
</div>
</div>
</teleport>
</template>
- 异步用法,异步用法其实就是使用reveal方法的时候,直接将确认关闭弹窗或者取消关闭弹窗之后的操作放在后面,而确认关闭弹窗和取消关闭弹窗都会将data和是否取消的状态进行更新。这样就能根据isCanceled的状态来判断是确认关闭还是取消关闭。
<script setup>
import { useConfirmDialog } from '@vueuse/core'
const {
isRevealed,
reveal,
confirm,
cancel,
} = useConfirmDialog()
const openDialog = async () => {
const { data, isCanceled } = await reveal()
if (!isCanceled) {
console.log(data)
}
}
</script>
<template>
<button @click="openDialog">Show Modal</button>
<teleport to="body">
<div v-if="isRevealed" class="modal-layout">
<div class="modal">
<h2>Confirm?</h2>
<button @click="confirm(true)">Yes</button>
<button @click="confirm(false)">No</button>
</div>
</div>
</teleport>
</template>
附上流程图 —— 打开弹窗,执行操作,确认关闭弹窗
总结
- 当需要在弹窗的状态变化时,执行想要的操作(函数),就能使用这个钩子函数,好处就在于避免重复编写类似的逻辑,有利于代码的规范,以及方便查找状态变化时的方法,从而更好地维护代码,降低出错率。
- 同时这个钩子函数的实现其实也是一个设计模式的体现,大家可以了解一下模板模式
© 版权声明
文章版权归作者所有,未经允许请勿转载,侵权请联系 admin@trc20.tw 删除。
THE END