ポップアップ ウィンドウは、フロントエンド開発における一般的な要件です。 Element UI フレームワークのコンポーネントel-dialog
は、ポップアップ ウィンドウに関連する基本的な機能を提供します。ただし、実際の開発では、プロジェクト内のスタイルと動作を均一に管理するためのポップアップ ウィンドウの二次カプセル化など、いくつかのカスタマイズ要件が必然的に発生します。
useDialog
この記事では、フックのカプセル化を使用してel-dialog
、より柔軟で使いやすいポップアップ コンポーネントを実現する方法を紹介します。
1. 課題の明確化
「共通のコンポーネントを複数のページに適用する」というのは、非常に一般的な実際的なシナリオです。
例: アプリケーションの購入を例に挙げます。ユーザーは支払いページで購入することも、他のページを閲覧中に購入リクエストをトリガーすることもできます。この場合、ユーザーをガイドするダイアログ ボックスが表示される必要があります。購入行動を完了します。
この機能を実現するために、これまでは通常、次の手順が行われてきました。
- 購入コンポーネントをカプセル化する: まず、さまざまなページやシナリオで再利用できるように、一般的な購入コンポーネントを作成します。
- 支払いページに購入コンポーネントをレンダリングする: 購入コンポーネントを支払いページに直接埋め込みます。
el-dialog
他のページでの購入コンポーネントの表示を使用します。他のページでのel-dialog
コンポーネントの表示を制御し、visible
状態変数 (通常はref
応答変数) を使用して、ダイアログ ボックスのポップアップと閉じるを動的に制御します。
この方法でも機能要件は満たせますが、コンポーネントを使用するページや機能が増えると、メンテナンスがより複雑で面倒になります。使用するページが増えるたびに、表示/非表示を制御するロジックを繰り返しコードで記述する必要があります。
では、このプロセスを簡素化するもっと良い方法はあるのでしょうか?別の関数を使用して、何らかの方法で購入コンポーネントの開閉をグローバルに制御し、それによってコードの重複とメンテナンスのコストを削減することは可能ですか?
2. useDialogフックについて
Vue では、フックを使用して Vue の機能を機能コンポーネントまたは API に「フック」できます。これらは通常、Vue によって提供される応答性が高く再利用可能なロジック関数のセットである、Composition API で使用されます。
この記事で説明するフックはuseDialog
、コンポーネントの基本機能をカプセル化するカスタム フックでありel-dialog
、プロジェクト内のポップアップ ウィンドウを管理および表示するための追加機能を提供することもできます。
3. useDialogフックを実装する
useDialog
フックは次の目標を達成する必要があります。
- 基本的な使用法、渡される基本属性、およびデフォルトのスロット、エクスポート、および関数
el-dialog
によって表示されるコンテンツを満たします。openDialog
closeDialog
- サポートされて
el-dialog
いるイベント構成。 - デフォルトの
slot
コンポーネント属性構成をサポートします。 - など
el-dialog
の他のスロット構成をサポートします。header
footer
- コンテンツ コンポーネントで特定のイベントをスローすると、ダイアログを閉じることがサポートされます。
- サポートされている表示コンテンツは
jsx
、普通文本
、Vue Component
、です。 - 表示されたコンテンツを閉じることができるかどうかを制御するコールバック関数をサポートします
beforeClose
。 - フックの前の表示をサポートします。たとえば
onBeforeOpen
、 - 定義中およびポップアップ中の構成プロパティの変更をサポートします。
- root vue のプロトタイプの継承をサポートし、 ; などの関数を使用でき
vue-i18n
ます$t
。 ts
パラメータプロンプトをサポートします。
(1)useDialog.ts
ファイル実装型定義の準備
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) 通常useDialog
機能の実装
次の関数は、目標 1、2、3、4、6、および 11 を含む基本的な使用法を実装します。
目標 1: 基本的な使用法を満たし、
el-dialog
基本的な属性とデフォルトのスロット表示コンテンツ、エクスポートopenDialog
およびcloseDialog
関数を渡す。 目標 2:イベント構成をサポートする。目標
3 .: デフォルトのコンポーネントの属性構成をサポートする。目標 6: 、 、 の表示コンテンツをサポートする。目標11 :パラメータ プロンプトをサポートする。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) 目標5の達成
目標 5: コンテンツ コンポーネントに特定のイベントをスローして、ダイアログを閉じることをサポートします。
- 定義でサポートされています
closeEventName
。
interface Options<P> {
// ...
closeEventName?: string // 新增的属性
}
- ダイアログを閉じるイベント
useDialog
を受け取る関数を変更します。closeEventName
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) 目標7、8の達成
目標 7: 表示されたコンテンツ内で閉じることができるかどうかを制御するコールバック関数をサポートします。たとえば
beforeClose
、
目標 8: 表示前のフックをサポートしますonBeforeOpen
。
- これは定義でサポートされており
onBeforeOpen
、beforeCloseDialog
コンポーネント呼び出し設定とともにデフォルトでコンテンツ コンポーネントに渡されます。
type DialogProps = ElDialogInstance['$props'] & {
onBeforeOpen?: () => boolean | void
}
- イベントを受信して渡すよう
useDialog
に関数を変更します。onBeforeOpen
beforeCloseDialog
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) 目標9、10の達成
目標 9: 定義およびポップアップ時の構成プロパティの変更をサポートする。目標 10: root vue のプロトタイプの継承をサポートし、次のような関数
を使用できる。vue-i18n
$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 }
}
上記のパッケージを介してフックを使用した後useDialog
、ウィンドウをポップアップする必要がある場合は、フックを導入してopenDialog
メソッドを呼び出すだけで済み、非常に便利で簡潔です。さらに、このようなカプセル化により、後でポップアップ ウィンドウのロジックを変更useDialog
する必要があり、1 つずつ編集する必要がなくなります。
4. UseDialog フックケースの練習
次に、useDialog
Hook を使用して、冒頭で述べたアプリケーション購入の問題を解決します。
(1)components/buy.vue
購入コンポーネントの作成
<script lang="ts" setup>
const props = defineProps({
from: {
type: String,
default: '',
},
})
</script>
<template>
我是购买组件
</template>
(2)ページ内の購入コンポーネントpages/subscription.vue
を使用するbuy.vue
<script lang="ts" setup>
import Buy from '@/components/buy.vue'
</script>
<template>
<Buy from="subscription" />
</template>
(3)他機能ページへのポップアップbuy.vue
購入コンポーネント
<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>
拡張機能: useDialog フックのその他のアプリケーション
beforeClose
&closeEventName
例:buy.vue
コンポーネントの購入
<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>
要約する
useDialog
フックのカプセル化を使用すると、el-dialog
フロントエンド テクノロジをより興味深く、簡潔にすることができます。また、作者は、フロントエンド コードをよりエレガントで保守しやすくするために、誰もがこの種のカプセル化方法を試してほしいと考えています。
優秀なエンジニアは優秀なシェフと同じで、あらゆる料理を美味しく仕上げる絶妙な調理技術と味付け技術を習得しています。
LigaAI は開発者文化の維持と構築を非常に重視しており、今後もより多くのテクノロジー共有と興味深いテクノロジー実践を共有していきます。
LigaAI アカウントのフォローへようこそ。新世代のインテリジェントな R&D コラボレーション プラットフォームをクリックして、私たちとさらに交流できることを楽しみにしています。
開発者の出発を支援するために、LigaAI は皆さんと一緒に旅をすることを楽しみにしています。
1990 年代生まれのプログラマーがビデオ移植ソフトウェアを開発し、1 年足らずで 700 万以上の利益を上げました。結末は非常に罰的でした。 高校生が成人式にオープンソースプログラミング言語を自作―ネチズンの鋭いコメント: 詐欺横行でRustDesk依存、国内サービスの タオバオ(taobao.com)は国内サービスを一時停止、ウェブ版の最適化作業を再開 Java最も一般的に使用されている Java LTS バージョンは 17 、Windows 11 は減少し続ける Open Source Daily | Google がオープンソースの Rabbit R1 を支持、Microsoft の不安と野心; Electricがオープンプラットフォームを閉鎖 AppleがM4チップをリリース GoogleがAndroidユニバーサルカーネル(ACK)を削除 RISC-Vアーキテクチャのサポート Yunfengがアリババを辞任し、将来的にはWindowsプラットフォームで独立したゲームを制作する予定