Popup-Fenster sind eine häufige Anforderung in der Frontend-Entwicklung. Die Komponenten im Element UI-Framework el-dialog
stellen grundlegende Funktionen im Zusammenhang mit Popup-Fenstern bereit. In der tatsächlichen Entwicklung werden wir jedoch zwangsläufig auf einige benutzerdefinierte Anforderungen stoßen, z. B. auf die sekundäre Kapselung von Popup-Fenstern, um Stile und Verhaltensweisen im Projekt einheitlich zu verwalten.
In diesem Artikel erfahren Sie, wie Sie mithilfe useDialog
der Hook-Kapselung el-dialog
eine flexiblere und benutzerfreundlichere Popup-Komponente erhalten.
1. Klärung von Problemen
„Eine gemeinsame Komponente auf mehrere Seiten anwenden“ ist ein sehr häufiges praktisches Szenario.
Nehmen wir als Beispiel den Kauf einer Anwendung. Der Benutzer kann auf der Zahlungsseite einen Kauf tätigen oder beim Durchsuchen anderer Seiten eine Kaufanfrage auslösen um das Kaufverhalten zu vervollständigen.
Um diese Funktionalität zu erreichen, wurden in der Vergangenheit typischerweise die folgenden Schritte unternommen:
- Kapseln Sie die Kaufkomponente : Erstellen Sie zunächst eine allgemeine Kaufkomponente, damit diese auf verschiedenen Seiten und in verschiedenen Szenarien wiederverwendet werden kann.
- Rendern Sie die Kaufkomponente auf der Zahlungsseite : Betten Sie die Kaufkomponente direkt in die Zahlungsseite ein.
- Verwenden Sie
el-dialog
die Anzeigekaufkomponenteel-dialog
auf anderen Seiten: Steuern Sie die Anzeige der Komponente auf anderen Seiten und verwenden Sievisible
Statusvariablen (normalerweise eineref
reaktionsfähige Variable), um das Popup und Schließen des Dialogfelds dynamisch zu steuern.
Obwohl diese Methode funktionale Anforderungen erfüllen kann, wird die Wartung komplexer und umständlicher, da die Komponente von immer mehr Seiten und Funktionen verwendet wird – für jede zusätzliche Seite, die verwendet wird, muss die Logik zur Steuerung des Anzeigens/Ausblendens wiederholt Code geschrieben werden.
Gibt es also eine bessere Möglichkeit, diesen Prozess zu vereinfachen? Ist es möglich, eine separate Funktion zu verwenden, um das Öffnen und Schließen der Kaufkomponente auf irgendeine Weise global zu steuern und dadurch Codeduplizierung und Wartungskosten zu reduzieren?
2. Über useDialog Hook
In Vue ermöglichen Hooks das „Einbinden“ von Vue-Funktionen in Funktionskomponenten oder APIs. Sie werden normalerweise in der Composition API verwendet, einer Reihe reaktionsfähiger und wiederverwendbarer Logikfunktionen, die von Vue bereitgestellt werden.
Der in diesem Artikel erwähnte Hook useDialog
ist ein benutzerdefinierter Hook, der el-dialog
die Grundfunktionen der Komponente kapselt. Er kann auch zusätzliche Funktionen zum Verwalten und Anzeigen von Popup-Fenstern im Projekt bereitstellen.
3. Implementieren Sie den useDialog-Hook
useDialog
Hook muss folgende Ziele erreichen:
- Erfüllen Sie die grundlegende Verwendung,
el-dialog
die übergebenen grundlegenden Attribute und den angezeigten Inhalt durch den Standard-Slot, den ExportopenDialog
undcloseDialog
die Funktionen; - Unterstützte
el-dialog
Ereigniskonfiguration; - Unterstützt
slot
die Standardkonfiguration von Komponentenattributen; - Unterstützt
el-dialog
andere Steckplatzkonfigurationen wieheader
uswfooter
.; - Das Auslösen bestimmter Ereignisse in Inhaltskomponenten unterstützt das Schließen des Dialogs.
- Unterstützte Anzeigeinhalte sind
jsx
,普通文本
,Vue Component
; - Unterstützt Rückruffunktionen, die beispielsweise steuern, ob der angezeigte Inhalt geschlossen werden kann
beforeClose
; - Unterstützt beispielsweise die Anzeige vor Hooks
onBeforeOpen
; - Unterstützt das Ändern von Konfigurationseigenschaften während der Definition und beim Popup;
- Unterstützt das Erben des Root-Vue-Prototyps. Sie können Funktionen
vue-i18n
wie ;$t
- Support-
ts
Parameter-Eingabeaufforderung;
(1) Bereiten Sie useDialog.ts
die Definition des Dateiimplementierungstyps vor
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) Implementieren Sie gewöhnliche useDialog
Funktionen
Die folgenden Funktionen implementieren die grundlegende Verwendung, einschließlich der Ziele 1, 2, 3, 4, 6 und 11.
el-dialog
Ziel 1: Grundlegende Nutzung erfüllen, grundlegende Attribute und Standard-Slot-Anzeigeinhalte, ExportopenDialog
undcloseDialog
Funktionen übergeben ; Ziel 3: Attributkonfiguration von Standardkomponenten unterstützen , wie
z . B. und usw.; Ziel 6: Anzeigeinhalt von unterstützen ; Ziel 11 : Parameteraufforderungen unterstützen;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) Ziel 5 erreichen
Ziel 5: Spezifische Ereignisse in Inhaltskomponenten werfen, um das Schließen des Dialogs zu unterstützen;
- in der Definition unterstützt
closeEventName
;
interface Options<P> {
// ...
closeEventName?: string // 新增的属性
}
- Ändern Sie
useDialog
die Funktion, umcloseEventName
das Ereignis zum Schließen des Dialogs zu empfangen.
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) Ziele 7 und 8 erreichen
Ziel 7: Unterstützen Sie eine Rückruffunktion, die beispielsweise steuert, ob sie im angezeigten Inhalt geschlossen werden kann
beforeClose
.
Ziel 8: Unterstützen Sie Hooks vor der AnzeigeonBeforeOpen
.
- Es wird in der Definition unterstützt
onBeforeOpen
undbeforeCloseDialog
standardmäßig mit Komponentenaufrufeinstellungen an die Inhaltskomponente übergeben.
type DialogProps = ElDialogInstance['$props'] & {
onBeforeOpen?: () => boolean | void
}
- Ändern Sie
useDialog
die Funktion, umonBeforeOpen
das Ereignis zu empfangen und weiterzuleitenbeforeCloseDialog
.
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) Ziele 9 und 10 erreichen
Ziel 9: Unterstützen Sie das Ändern von Konfigurationseigenschaften beim Definieren und Öffnen.
Ziel 10: Unterstützen Sie das Erben des Root-Vue-Prototyps. Sie können Funktionenvue-i18n
wie ;$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 }
}
Nachdem Sie Hook über das obige Paket verwendet haben useDialog
, müssen Sie beim Öffnen eines Fensters nur Hook einführen und openDialog
die Methode aufrufen, was sehr praktisch und prägnant ist. Darüber hinaus erleichtert eine solche Kapselung die spätere Änderung der Popup-Fensterlogik. Sie müssen useDialog
sie nur in Hook ändern, ohne sie einzeln bearbeiten zu müssen.
4. Verwenden Sie die Dialog-Hook-Fallpraxis
Als nächstes verwenden wir useDialog
Hook, um das eingangs erwähnte Anwendungskaufproblem zu lösen.
(1) components/buy.vue
Einkaufskomponente erstellen
<script lang="ts" setup>
const props = defineProps({
from: {
type: String,
default: '',
},
})
</script>
<template>
我是购买组件
</template>
(2) pages/subscription.vue
Verwenden Sie buy.vue
die Kaufkomponente auf der Seite
<script lang="ts" setup>
import Buy from '@/components/buy.vue'
</script>
<template>
<Buy from="subscription" />
</template>
buy.vue
(3) Popup- Kaufkomponenten auf anderen Funktionsseiten
<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>
Erweiterung: andere Anwendungen von useDialog Hook
beforeClose
& closeEventName
Beispiel: buy.vue
Komponenten kaufen
<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>
Zusammenfassen
Die Verwendung useDialog
der Hook-Kapselung el-dialog
kann die Front-End-Technologie interessanter und prägnanter machen. Der Autor hofft auch, dass jeder diese Kapselungsmethode ausprobieren kann, um den Front-End-Code eleganter und einfacher zu warten.
Exzellente Ingenieure sind wie exzellente Köche. Sie beherrschen exquisite Koch- und Würzkünste, um jedes Gericht köstlich zu machen!
LigaAI legt großen Wert auf die Pflege und den Aufbau der Entwicklerkultur und wird weiterhin mehr Technologieaustausch und interessante Technologiepraktiken teilen.
Um Entwicklern dabei zu helfen, die Segel zu setzen, freut sich LigaAI darauf, Sie auf dem gesamten Weg zu begleiten!
Ein in den 1990er Jahren geborener Programmierer hat eine Videoportierungssoftware entwickelt und in weniger als einem Jahr über 7 Millionen verdient. Das Ende war sehr bestrafend! High-School-Schüler erstellen im Rahmen einer Coming-of-Age-Zeremonie ihre eigene Open-Source-Programmiersprache – scharfe Kommentare von Internetnutzern: Der inländische Dienst Taobao (taobao.com) verließ sich aufgrund des grassierenden Betrugs auf RustDesk und stellte die inländischen Dienste ein und startete die Arbeit zur Optimierung der Webversion von Java neu 17 ist die am häufigsten verwendete Java LTS-Version. Windows 11 erreicht weiterhin einen Rückgang. Open Source Daily unterstützt die Übernahme von Open Source Rabbit R1; Electric schließt die offene Plattform Apple veröffentlicht M4-Chip Google löscht Android Universal Kernel (ACK) Unterstützung für RISC-V-Architektur Yunfeng ist von Alibaba zurückgetreten und plant, in Zukunft unabhängige Spiele auf der Windows-Plattform zu produzieren