Las ventanas emergentes son un requisito común en el desarrollo front-end. Los componentes en el marco de Element UI el-dialog
proporcionan funciones básicas relacionadas con las ventanas emergentes. Sin embargo, en el desarrollo real, inevitablemente encontraremos algunos requisitos personalizados, como la encapsulación secundaria de ventanas emergentes para administrar de manera uniforme los estilos y comportamientos en el proyecto.
Este artículo compartirá cómo utilizar useDialog
la encapsulación Hook el-dialog
para lograr un componente emergente más flexible y fácil de usar.
1. Aclaración de cuestiones
"Aplicar un componente común a varias páginas" es un escenario práctico muy común.
Por ejemplo: tome la compra de una aplicación como ejemplo. El usuario puede realizar una compra en la página de pago o puede activar una solicitud de compra mientras navega por otras páginas. En este caso, es necesario que aparezca un cuadro de diálogo para guiar al usuario. para completar el comportamiento de compra.
Para lograr esta funcionalidad, normalmente se han seguido los siguientes pasos en el pasado:
- Encapsule el componente de compra : primero cree un componente de compra general para que pueda reutilizarse en diferentes páginas y escenarios.
- Representar el componente de compra en la página de pago : incruste el componente de compra directamente en la página de pago.
- Utilice
el-dialog
el componente de compra de visualizaciónel-dialog
en otras páginas: controle la visualización del componente en otras páginas y utilicevisible
variables de estado (generalmente unaref
variable de respuesta) para controlar dinámicamente la ventana emergente y el cierre del cuadro de diálogo.
Aunque este método puede cumplir con los requisitos funcionales, a medida que el componente es utilizado por más y más páginas y funciones, el mantenimiento se volverá más complejo y engorroso: para cada página adicional de uso, la lógica para controlar la visualización/ocultación debe escribirse repetidamente en código.
Entonces, ¿existe una mejor manera de simplificar este proceso? ¿Es posible utilizar una función separada para controlar globalmente la apertura y el cierre del componente de compra de alguna manera, reduciendo así la duplicación de código y los costos de mantenimiento?
2. Acerca del usoDialog Hook
En Vue, los Hooks permiten "enganchar" características de Vue en componentes funcionales o API. Por lo general, se usan en la API de composición, que es un conjunto de funciones lógicas receptivas y reutilizables proporcionadas por Vue.
El Hook mencionado en este artículo useDialog
es un Hook personalizado que encapsula el-dialog
las funciones básicas del componente. También puede proporcionar funciones adicionales para administrar y mostrar ventanas emergentes en el proyecto.
3. Implementar useDialog Hook
useDialog
Hook necesita lograr los siguientes objetivos:
- Conozca el uso básico,
el-dialog
los atributos básicos pasados y el contenido mostrado por la ranura, exportaciónopenDialog
ycloseDialog
funciones predeterminadas; el-dialog
Configuración de eventos admitida ;- Admite
slot
la configuración de atributos de componentes predeterminados; - Admite
el-dialog
otras configuraciones de ranura, comoheader
yfooter
etc.; - Lanzar eventos específicos en componentes de contenido permite cerrar el diálogo;
- El contenido de visualización admitido es
jsx
,普通文本
,Vue Component
; - Admite funciones de devolución de llamada que controlan si el contenido mostrado se puede cerrar, por ejemplo
beforeClose
; - Admite visualización antes de los ganchos, por ejemplo
onBeforeOpen
; - Admite la modificación de propiedades de configuración durante la definición y la ventana emergente;
- Admite heredar el prototipo de root vue, puede utilizar funciones
vue-i18n
como ;$t
- Aviso de parámetro de soporte
ts
;
(1) Preparar useDialog.ts
la definición del tipo de implementación del archivo
import type { Ref } from 'vue'
import { h, render } from 'vue'
import { ElDialog } from 'element-plus'
import type {
ComponentInternalInstance,
} from '@vue/runtime-core'
type Content = Parameters<typeof h>[0] | string | JSX.Element
// 使用 InstanceType 获取 ElDialog 组件实例的类型
type ElDialogInstance = InstanceType<typeof ElDialog>
// 从组件实例中提取 Props 类型
type DialogProps = ElDialogInstance['$props'] & {
}
interface ElDialogSlots {
header?: (...args: any[]) => Content
footer?: (...args: any[]) => Content
}
interface Options<P> {
dialogProps?: DialogProps
dialogSlots?: ElDialogSlots
contentProps?: P
}
(2) Implementar useDialog
funciones ordinarias
Las siguientes funciones implementan el uso básico, incluidos los objetivos 1, 2, 3, 4, 6 y 11.
Objetivo 1: cumplir con el uso básico, transferir
el-dialog
atributos básicos y contenido, exportaciónopenDialog
ycloseDialog
funciones de visualización de ranuras predeterminadas.
Meta 2: admitirel-dialog
la configuración de eventos. Meta 3
.: admitir la configuración de atributos de losslot
componentes predeterminados; etc. Objetivo 6: Soporte de visualización de contenido de , , ; Objetivo 11: Soporte de indicaciones de parámetros;el-dialog
header
footer
jsx
普通文本
Vue Component
ts
export function useDialog<P = any>(content: Content, options?: Ref<Options<P>> | Options<P>) {
let dialogInstance: ComponentInternalInstance | null = null
let fragment: Element | null = null
// 关闭并卸载组件
const closeAfter = () => {
if (fragment) {
render(null, fragment as unknown as Element) // 卸载组件
fragment.textContent = '' // 清空文档片段
fragment = null
}
dialogInstance = null
}
function closeDialog() {
if (dialogInstance)
dialogInstance.props.modelValue = false
}
// 创建并挂载组件
function openDialog() {
if (dialogInstance) {
closeDialog()
closeAfter()
}
const { dialogProps, contentProps } = options
fragment = document.createDocumentFragment() as unknown as Element
const vNode = h(ElDialog, {
...dialogProps,
modelValue: true,
onClosed: () => {
dialogProps?.onClosed?.()
closeAfter()
},
}, {
default: () => [typeof content === 'string'
? content
: h(content as any, {
...contentProps,
})],
...options.dialogSlots,
})
render(vNode, fragment)
dialogInstance = vNode.component
document.body.appendChild(fragment)
}
onUnmounted(() => {
closeDialog()
})
return { openDialog, closeDialog }
}
(3) Lograr el Objetivo 5
Objetivo 5: lanzar eventos específicos en componentes de contenido para permitir el cierre del diálogo;
- apoyado en la definición
closeEventName
;
interface Options<P> {
// ...
closeEventName?: string // 新增的属性
}
- Modifique
useDialog
la función para recibircloseEventName
el evento para cerrar el diálogo.
export function useDialog<P = any>(content: Content, options?: Ref<Options<P>> | Options<P>) {
// ...
// 创建并挂载组件
function openDialog() {
// ...
fragment = document.createDocumentFragment() as unknown as Element
// 转换closeEventName事件
const closeEventName = `on${upperFirst(_options?.closeEventName || 'closeDialog')}`
const vNode = h(ElDialog, {
// ...
}, {
default: () => [typeof content === 'string'
? content
: h(content as any, {
...contentProps,
[closeEventName]: closeDialog, // 监听自定义关闭事件,并执行关闭
})],
...options.dialogSlots,
})
render(vNode, fragment)
dialogInstance = vNode.component
document.body.appendChild(fragment)
}
onUnmounted(() => {
closeDialog()
})
return { openDialog, closeDialog }
}
(4) Lograr los Objetivos 7 y 8
Objetivo 7: admitir una función de devolución de llamada que controle si se puede cerrar en el contenido mostrado, por ejemplo
beforeClose
Objetivo
8: admitir ganchos antes de mostrar, por ejemploonBeforeOpen
;
- Se admite en la definición
onBeforeOpen
ybeforeCloseDialog
se pasa al componente de contenido de forma predeterminada, con la configuración de llamada del componente;
type DialogProps = ElDialogInstance['$props'] & {
onBeforeOpen?: () => boolean | void
}
- Modifique
useDialog
la función para recibironBeforeOpen
el evento y transmitirlobeforeCloseDialog
.
export function useDialog<P = any>(content: Content, options?: Ref<Options<P>> | Options<P>) {
// ...
// 创建并挂载组件
function openDialog() {
// ...
const { dialogProps, contentProps } = options
// 调用before钩子,如果为false则不打开
if (dialogProps?.onBeforeOpen?.() === false) {
return
}
// ...
// 定义当前块关闭前钩子变量
let onBeforeClose: (() => Promise<boolean | void> | boolean | void) | null
const vNode = h(ElDialog, {
// ...
beforeClose: async (done) => {
// 配置`el-dialog`的关闭回调钩子函数
const result = await onBeforeClose?.()
if (result === false) {
return
}
done()
},
onClosed: () => {
dialogProps?.onClosed?.()
closeAfter()
// 关闭后回收当前变量
onBeforeClose = null
},
}, {
default: () => [typeof content === 'string'
? content
: h(content as any, {
// ...
beforeCloseDialog: (fn: (() => boolean | void)) => {
// 把`beforeCloseDialog`传递给`content`,当组件内部使用`props.beforeCloseDialog(fn)`时,会把fn传递给`onBeforeClose`
onBeforeClose = fn
},
})],
...options.dialogSlots,
})
render(vNode, fragment)
dialogInstance = vNode.component
document.body.appendChild(fragment)
}
onUnmounted(() => {
closeDialog()
})
return { openDialog, closeDialog }
}
(5) Lograr los objetivos 9 y 10
Objetivo 9: admitir la modificación de propiedades de configuración al definir y abrir.
Objetivo 10: admitir la herencia del prototipo de root vue, puede utilizar funcionesvue-i18n
como ;$t
// 定义工具函数,获取计算属性的option
function getOptions<P>(options?: Ref<Options<P>> | Options<P>) {
if (!options)
return {}
return isRef(options) ? options.value : options
}
export function useDialog<P = any>(content: Content, options?: Ref<Options<P>> | Options<P>) {
// ...
// 获取当前组件实例,用于设置当前dialog的上下文,继承prototype
const instance = getCurrentInstance()
// 创建并挂载组件,新增`modifyOptions`参数
function openDialog(modifyOptions?: Partial<Options<P>>) {
// ...
const _options = getOptions(options)
// 如果有修改,则合并options。替换之前的options变量为 _options
if (modifyOptions)
merge(_options, modifyOptions)
// ...
const vNode = h(ElDialog, {
// ...
}, {
// ...
})
// 设置当前的上下文为使用者的上下文
vNode.appContext = instance?.appContext || null
render(vNode, fragment)
dialogInstance = vNode.component
document.body.appendChild(fragment)
}
onUnmounted(() => {
closeDialog()
})
return { openDialog, closeDialog }
}
Después de usar Hook a través del paquete anterior useDialog
, cuando se necesita una ventana emergente, solo necesita introducir Hook y llamar openDialog
al método, lo cual es muy conveniente y conciso. Además, dicha encapsulación también hará que sea más conveniente modificar la lógica de la ventana emergente más adelante. Solo necesita modificarla useDialog
en Hook, sin tener que editarla una por una.
4. Práctica de casos de UseDialog Hook
A continuación, utilizamos useDialog
Hook para solucionar el problema de compra de la aplicación mencionado al principio.
(1) Crear components/buy.vue
componente de compra
<script lang="ts" setup>
const props = defineProps({
from: {
type: String,
default: '',
},
})
</script>
<template>
我是购买组件
</template>
(2) pages/subscription.vue
Utilice buy.vue
el componente de compra en la página.
<script lang="ts" setup>
import Buy from '@/components/buy.vue'
</script>
<template>
<Buy from="subscription" />
</template>
buy.vue
(3) Componentes de compra emergentes en otras páginas de funciones
<script lang="ts" setup>
import { useDialog } from '@/hooks/useDialog'
const Buy = defineAsyncComponent(() => import('@/components/buy.vue'))
const { openDialog } = useDialog(Buy, {
dialogProps: {
// ...
title: '购买'
},
contentProps: {
from: 'function',
},
})
const onSomeClick = () => {
openDialog()
}
</script>
Extensión: otras aplicaciones de usoDialog Hook
beforeClose
& closeEventName
Ejemplo: buy.vue
comprar componentes
<script lang="ts" setup>
const props = defineProps({
from: {
type: String,
default: '',
},
beforeCloseDialog: {
type: Function,
default: () => true,
},
})
const emit = defineEmits(['closeDialog'])
props.beforeCloseDialog(() => {
// 假如from 为 空字符串不能关闭
if (!props.from) {
return false
}
return true
})
// 关闭dialog
const onBuySuccess = () => emit('closeDialog')
</script>
<script lang="ts" setup>
import { useDialog } from '@/hooks/useDialog'
const Buy = defineAsyncComponent(() => import('@/components/buy.vue'))
const { openDialog } = useDialog(Buy, {
dialogProps: {
// ...
title: '购买'
},
contentProps: {
from: '',
},
})
const onSomeClick = () => {
openDialog()
}
</script>
Resumir
El uso de useDialog
la encapsulación Hook el-dialog
puede hacer que la tecnología front-end sea más interesante y concisa. El autor también espera que todos puedan probar este tipo de método de encapsulación para hacer que el código front-end sea más elegante y fácil de mantener.
Los excelentes ingenieros son como excelentes chefs. ¡Dominan exquisitas habilidades culinarias y condimentadoras para hacer que cada plato sea delicioso!
LigaAI concede gran importancia al mantenimiento y construcción de la cultura de los desarrolladores y continuará compartiendo más tecnología y prácticas tecnológicas interesantes.
Para ayudar a los desarrolladores a zarpar, ¡LigaAI espera viajar contigo durante todo el camino!
Un programador nacido en los años 90 desarrolló un software de portabilidad de vídeo y ganó más de 7 millones en menos de un año. ¡El final fue muy duro! Los estudiantes de secundaria crean su propio lenguaje de programación de código abierto como una ceremonia de mayoría de edad: comentarios agudos de los internautas: debido al fraude desenfrenado, confiando en RustDesk, el servicio doméstico Taobao (taobao.com) suspendió los servicios domésticos y reinició el trabajo de optimización de la versión web Java 17 es la versión Java LTS más utilizada. Cuota de mercado de Windows 10. Alcanzando el 70%, Windows 11 continúa disminuyendo. Open Source Daily | Google apoya a Hongmeng para hacerse cargo de los teléfonos Android de código abierto respaldados por Docker; Electric cierra la plataforma abierta Apple lanza el chip M4 Google elimina el kernel universal de Android (ACK) Soporte para la arquitectura RISC-V Yunfeng renunció a Alibaba y planea producir juegos independientes en la plataforma Windows en el futuro