Janelas pop-up são um requisito comum no desenvolvimento front-end. Os componentes da estrutura Element UI el-dialog
fornecem funções básicas relacionadas às janelas pop-up. No entanto, no desenvolvimento real, inevitavelmente encontraremos alguns requisitos personalizados, como encapsulamento secundário de janelas pop-up para gerenciar uniformemente estilos e comportamentos no projeto.
Este artigo compartilhará como usar useDialog
o encapsulamento Hook el-dialog
para obter um componente pop-up mais flexível e fácil de usar.
1. Esclarecimento de questões
“Aplicar um componente comum a múltiplas páginas” é um cenário prático muito comum.
Por exemplo: Tomemos como exemplo a compra de um aplicativo. O usuário pode fazer uma compra na página de pagamento ou pode acionar uma solicitação de compra enquanto navega em outras páginas. para concluir o comportamento de compra.
Para alcançar essa funcionalidade, as seguintes etapas normalmente foram executadas no passado:
- Encapsule o componente de compra : primeiro crie um componente de compra geral para que possa ser reutilizado em diferentes páginas e cenários.
- Renderize o componente de compra na página de pagamento : incorpore o componente de compra diretamente na página de pagamento.
- Use
el-dialog
o componente de compra de exibiçãoel-dialog
em outras páginas: controle a exibição do componente em outras páginas e usevisible
variáveis de estado (geralmente umaref
variável responsiva) para controlar dinamicamente o pop-up e o fechamento da caixa de diálogo.
Embora este método possa atender aos requisitos funcionais, à medida que o componente é usado por mais e mais páginas e funções, a manutenção se tornará mais complexa e complicada - para cada página adicional de uso, a lógica para controlar a exibição/ocultação deve ser escrita repetidamente no código.
Então, existe uma maneira melhor de simplificar esse processo? É possível utilizar uma função separada para controlar globalmente a abertura e o fechamento do componente de compra de alguma forma, reduzindo assim a duplicação de código e os custos de manutenção?
2. Sobre useDialog Gancho
No Vue, os Hooks permitem "conectar" recursos do Vue a componentes funcionais ou APIs. Eles geralmente são usados na API Composition, que é um conjunto de funções lógicas responsivas e reutilizáveis fornecidas pelo Vue.
O Hook mencionado neste artigo useDialog
é um Hook personalizado que encapsula el-dialog
as funções básicas do componente. Ele também pode fornecer recursos adicionais para gerenciar e exibir janelas pop-up no projeto.
3. Implementar o gancho useDialog
useDialog
Hook precisa atingir os seguintes objetivos:
- Conheça o uso básico,
el-dialog
os atributos básicos passados e o conteúdo exibido pelo slot padrão, exportaçãoopenDialog
ecloseDialog
funções; el-dialog
Configuração de eventos suportada ;- Suporta
slot
configuração de atributos de componentes padrão; - Suporta
el-dialog
outras configurações de slot, comoheader
efooter
etc.; - Lançar eventos específicos em componentes de conteúdo oferece suporte ao fechamento do diálogo;
- O conteúdo de exibição suportado é
jsx
,普通文本
,Vue Component
; - Suporta funções de retorno de chamada que controlam se o conteúdo exibido pode ser fechado, por exemplo
beforeClose
; - Suporta exibição antes de ganchos, por exemplo
onBeforeOpen
; - Suporta modificação de propriedades de configuração durante definição e pop-up;
- Suporta a herança do protótipo do root vue, você pode usar funções
vue-i18n
como ;$t
- Prompt de parâmetro de suporte
ts
;
(1) Preparar useDialog.ts
a definição do tipo de implementação de arquivo
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
funções comuns
As funções a seguir implementam o uso básico, incluindo as metas 1, 2, 3, 4, 6 e 11.
Meta 1: Atender ao uso básico, passar atributos
el-dialog
básicos e conteúdo de exibição de slot padrão , exportaçãoopenDialog
ecloseDialog
funções ; etc.; Meta 6: Suportar conteúdo de exibição de , , ; Meta 11: Suportar prompts de parâmetros;el-dialog
slot
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) Alcançar a Meta 5
Meta 5: Lançar eventos específicos em componentes de conteúdo para apoiar o fechamento do diálogo;
- apoiado na definição
closeEventName
;
interface Options<P> {
// ...
closeEventName?: string // 新增的属性
}
- Modifique
useDialog
a função para recebercloseEventName
o evento para fechar o 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) Alcançar as Metas 7 e 8
Meta 7: Apoiar uma função de retorno de chamada que controle se ela pode ser fechada no conteúdo exibido, por exemplo
beforeClose
;
Meta 8: Apoiar ganchos antes de exibir, por exemploonBeforeOpen
;
- É suportado na definição
onBeforeOpen
ebeforeCloseDialog
passado para o componente de conteúdo por padrão, com configurações de chamada de componente;
type DialogProps = ElDialogInstance['$props'] & {
onBeforeOpen?: () => boolean | void
}
- Modifique
useDialog
a função para receberonBeforeOpen
o evento e repassá-lobeforeCloseDialog
.
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) Alcançar as metas 9 e 10
Meta 9: Suportar a modificação de propriedades de configuração ao definir e aparecer;
Meta 10: Suportar a herança do protótipo do vue raiz, você pode usar funçõesvue-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 }
}
Depois de usar o Hook através do pacote acima useDialog
, quando precisar abrir uma janela, basta introduzir o Hook e chamar openDialog
o método, que é muito conveniente e conciso. Além disso, esse encapsulamento também tornará mais conveniente modificar a lógica da janela pop-up posteriormente. Você só precisa modificá- useDialog
la no Hook, sem precisar editá-la uma por uma.
4. Prática de caso UseDialog Hook
A seguir, usamos useDialog
o Hook para resolver o problema de compra do aplicativo mencionado no início.
(1) Criar components/buy.vue
componente de compra
<script lang="ts" setup>
const props = defineProps({
from: {
type: String,
default: '',
},
})
</script>
<template>
我是购买组件
</template>
(2) pages/subscription.vue
Use buy.vue
o componente de compra na página
<script lang="ts" setup>
import Buy from '@/components/buy.vue'
</script>
<template>
<Buy from="subscription" />
</template>
buy.vue
(3) Componentes de compra pop-up em outras páginas de funções
<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>
Extensão: outras aplicações do useDialog Hook
beforeClose
& closeEventName
Exemplo: 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
O uso useDialog
do encapsulamento Hook el-dialog
pode tornar a tecnologia front-end mais interessante e concisa. O autor também espera que todos possam experimentar esse tipo de método de encapsulamento para tornar o código front-end mais elegante e fácil de manter.
Excelentes engenheiros são como excelentes chefs. Eles dominam habilidades culinárias e de tempero requintadas para tornar cada prato delicioso!
A LigaAI atribui grande importância à manutenção e construção da cultura do desenvolvedor e continuará a compartilhar mais compartilhamento de tecnologia e práticas tecnológicas interessantes.
Para ajudar os desenvolvedores a navegar, a LigaAI espera viajar com você durante todo o caminho!
Um programador nascido na década de 1990 desenvolveu um software de portabilidade de vídeo e faturou mais de 7 milhões em menos de um ano. O final foi muito punitivo! Alunos do ensino médio criam sua própria linguagem de programação de código aberto como uma cerimônia de maioridade - comentários contundentes de internautas: Contando com RustDesk devido a fraude desenfreada, serviço doméstico Taobao (taobao.com) suspendeu serviços domésticos e reiniciou o trabalho de otimização de versão web Java 17 é a versão Java LTS mais comumente usada no mercado do Windows 10 Atingindo 70%, o Windows 11 continua a diminuir Open Source Daily | Google apoia Hongmeng para assumir o controle de telefones Android de código aberto apoiados pela ansiedade e ambição da Microsoft; Electric desliga a plataforma aberta Apple lança chip M4 Google exclui kernel universal do Android (ACK) Suporte para arquitetura RISC-V Yunfeng renunciou ao Alibaba e planeja produzir jogos independentes na plataforma Windows no futuro