Les fenêtres contextuelles sont une exigence courante dans le développement front-end. Les composants du framework Element UI el-dialog
fournissent des fonctions de base liées aux fenêtres contextuelles. Cependant, dans le développement réel, nous rencontrerons inévitablement certaines exigences personnalisées, telles que l'encapsulation secondaire des fenêtres contextuelles pour gérer uniformément les styles et les comportements dans le projet.
Cet article explique comment utiliser useDialog
l'encapsulation Hook el-dialog
pour obtenir un composant contextuel plus flexible et plus facile à utiliser.
1. Clarification des problèmes
« Appliquer un composant commun à plusieurs pages » est un scénario pratique très courant.
Par exemple : Prenons l'exemple de l'achat d'une application. L'utilisateur peut effectuer un achat sur la page de paiement, ou déclencher une demande d'achat en parcourant d'autres pages. Dans ce cas, une boîte de dialogue doit apparaître pour guider l'utilisateur. pour compléter le comportement d'achat.
Pour obtenir cette fonctionnalité, les étapes suivantes ont généralement été suivies dans le passé :
- Encapsuler le composant d'achat : Créez d'abord un composant d'achat général afin qu'il puisse être réutilisé sur différentes pages et scénarios.
- Afficher le composant d'achat sur la page de paiement : intégrez le composant d'achat directement dans la page de paiement.
- Utilisez
el-dialog
le composant d'achat d'affichageel-dialog
sur d'autres pages : contrôlez l'affichage du composant sur d'autres pages et utilisezvisible
des variables d'état (généralement uneref
variable réactive) pour contrôler dynamiquement le pop-up et la fermeture de la boîte de dialogue.
Bien que cette méthode puisse répondre aux exigences fonctionnelles, à mesure que le composant est utilisé par de plus en plus de pages et de fonctions, la maintenance deviendra plus complexe et fastidieuse - pour chaque page supplémentaire utilisée, la logique de contrôle de l'affichage/masquage doit être écrite à plusieurs reprises.
Alors, existe-t-il une meilleure façon de simplifier ce processus ? Est-il possible d'utiliser une fonction distincte pour contrôler globalement l'ouverture et la fermeture du composant d'achat d'une manière ou d'une autre, réduisant ainsi les coûts de duplication de code et de maintenance ?
2. À propos de l'utilisation du crochet de dialogue
Dans Vue, les Hooks permettent d'« accrocher » les fonctionnalités de Vue à des composants fonctionnels ou des API. Ils sont généralement utilisés dans l'API Composition, qui est un ensemble de fonctions logiques réactives et réutilisables fournies par Vue.
Le Hook mentionné dans cet article useDialog
est un Hook personnalisé qui encapsule el-dialog
les fonctions de base du composant. Il peut également fournir des fonctionnalités supplémentaires pour gérer et afficher les fenêtres contextuelles dans le projet.
3. Implémenter useDialog Hook
useDialog
Hook doit atteindre les objectifs suivants :
- Répondre à l'utilisation de base,
el-dialog
aux attributs de base transmis et au contenu affiché par l'emplacement par défaut, l'exportationopenDialog
etcloseDialog
les fonctions ; el-dialog
Configuration d'événement prise en charge ;- Prend en charge la configuration des attributs de composant par défaut
slot
; - Prend en charge
el-dialog
d'autres configurations de slot, telles queheader
etfooter
etc.; - Le lancement d'événements spécifiques dans des composants de contenu prend en charge la fermeture de la boîte de dialogue ;
- Le contenu d'affichage pris en charge est
jsx
,普通文本
,Vue Component
; - Prend en charge les fonctions de rappel qui contrôlent si le contenu affiché peut être fermé, par exemple
beforeClose
; - Prend en charge l'affichage avant les crochets, par exemple
onBeforeOpen
; - Prend en charge la modification des propriétés de configuration lors de la définition et de la fenêtre contextuelle ;
- Prend en charge l'héritage du prototype de root vue, vous pouvez utiliser des fonctions telles
vue-i18n
que ;$t
- Invite de paramètres de prise en charge
ts
;
(1) Préparer useDialog.ts
la définition du type d'implémentation de fichier
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) Implémenter useDialog
les fonctions ordinaires
Les fonctions suivantes implémentent une utilisation de base, notamment les objectifs 1, 2, 3, 4, 6 et 11.
Objectif 1 : Répondre à l'utilisation de base, transmettre les attributs de base et le contenu, l'exportation et les fonctions
el-dialog
d'affichage par défaut ; Objectif 2 : Prise en charge de la configuration des événements Objectif 3 : Prise en charge de la configuration des attributs des composants par défaut Objectif 4 : Prise en charge d'autres configurations d'emplacement, telles que et ; etc. ; Objectif 6 : Prise en charge du contenu d'affichage de , , ; Objectif 11 : Prise en charge des invites de paramètres ;openDialog
closeDialog
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) Atteindre l’objectif 5
Objectif 5 : lancer des événements spécifiques dans les composants de contenu pour prendre en charge la fermeture de la boîte de dialogue ;
- pris en charge dans la définition
closeEventName
;
interface Options<P> {
// ...
closeEventName?: string // 新增的属性
}
- Modifiez
useDialog
la fonction pour recevoircloseEventName
l'événement pour fermer la boîte de dialogue.
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) Atteindre les objectifs 7 et 8
Objectif 7 : Supporter une fonction de rappel qui contrôle s'il peut être fermé dans le contenu affiché, par exemple
beforeClose
;
Objectif 8 : Supporter les hooks avant l'affichage, par exempleonBeforeOpen
;
- Il est pris en charge dans la définition
onBeforeOpen
etbeforeCloseDialog
transmis au composant de contenu par défaut, avec les paramètres d'appel du composant ;
type DialogProps = ElDialogInstance['$props'] & {
onBeforeOpen?: () => boolean | void
}
- Modifiez
useDialog
la fonction pour recevoironBeforeOpen
l'événement et le transmettrebeforeCloseDialog
.
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) Atteindre les objectifs 9 et 10
Objectif 9 : Prise en charge de la modification des propriétés de configuration lors de la définition et de l'affichage ;
Objectif 10 : Prise en charge de l'héritage du prototype de root vue, vous pouvez utiliser des fonctions tellesvue-i18n
que ;$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 }
}
Après avoir utilisé Hook via le package ci-dessus useDialog
, lorsqu'une fenêtre contextuelle est nécessaire, il vous suffit d'introduire le Hook et d'appeler openDialog
la méthode, ce qui est très pratique et concis. De plus, une telle encapsulation rendra également plus pratique la modification ultérieure de la logique de la fenêtre contextuelle. Il vous suffit de useDialog
la modifier dans Hook, sans avoir à la modifier une par une.
4. Pratique de cas UseDialog Hook
Ensuite, nous utilisons useDialog
Hook pour résoudre le problème d'achat d'application mentionné au début.
(1) Créer components/buy.vue
un composant d'achat
<script lang="ts" setup>
const props = defineProps({
from: {
type: String,
default: '',
},
})
</script>
<template>
我是购买组件
</template>
(2) pages/subscription.vue
Utilisez buy.vue
le composant d'achat sur la page
<script lang="ts" setup>
import Buy from '@/components/buy.vue'
</script>
<template>
<Buy from="subscription" />
</template>
buy.vue
(3) Composants d'achat contextuels sur d'autres pages de fonctions
<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>
Extension : autres applications de useDialog Hook
beforeClose
& closeEventName
Exemple :buy.vue
achat de composants
<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>
Résumer
L’utilisation useDialog
de l’encapsulation Hook el-dialog
peut rendre la technologie frontale plus intéressante et plus concise. L'auteur espère également que tout le monde pourra essayer ce type de méthode d'encapsulation pour rendre le code frontal plus élégant et plus facile à maintenir.
Les excellents ingénieurs sont comme d’excellents chefs. Ils maîtrisent des compétences exquises en matière de cuisine et d’assaisonnement pour rendre chaque plat délicieux !
LigaAI attache une grande importance au maintien et à la construction de la culture des développeurs et continuera à partager davantage de partage de technologie et de pratiques technologiques intéressantes.
Pour aider les développeurs à mettre les voiles, LigaAI a hâte de voyager avec vous jusqu'au bout !
Un programmeur né dans les années 1990 a développé un logiciel de portage vidéo et en a réalisé plus de 7 millions en moins d'un an. La fin a été très éprouvante ! Des lycéens créent leur propre langage de programmation open source en guise de cérémonie de passage à l'âge adulte - commentaires acerbes des internautes : s'appuyant sur RustDesk en raison d'une fraude généralisée, le service domestique Taobao (taobao.com) a suspendu ses services domestiques et repris le travail d'optimisation de la version Web Java 17 est la version Java LTS la plus utilisée Part de marché de Windows 10 Atteignant 70 %, Windows 11 continue de décliner Open Source Daily | Google soutient Hongmeng pour prendre le relais des téléphones Android open source pris en charge par Docker ; Electric ferme la plate-forme ouverte Apple lance la puce M4 Google supprime le noyau universel Android (ACK) Prise en charge de l'architecture RISC-V Yunfeng a démissionné d'Alibaba et prévoit de produire des jeux indépendants sur la plate-forme Windows à l'avenir