Partage de technologie | Comment utiliser Hook pour encapsuler el-dialog dans le développement de fenêtres pop-up ?

Les fenêtres contextuelles sont une exigence courante dans le développement front-end. Les composants du framework Element UI el-dialogfournissent 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 useDialogl'encapsulation Hook el-dialogpour 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é :

  1. 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.
  2. Afficher le composant d'achat sur la page de paiement : intégrez le composant d'achat directement dans la page de paiement.
  3. Utilisez el-dialogle composant d'achat d'affichageel-dialog sur d'autres pages : contrôlez l'affichage du composant sur d'autres pages et utilisez visibledes variables d'état (généralement une refvariable 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 useDialogest un Hook personnalisé qui encapsule el-dialogles 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

useDialogHook doit atteindre les objectifs suivants :

  1. Répondre à l'utilisation de base, el-dialogaux attributs de base transmis et au contenu affiché par l'emplacement par défaut, l'exportation openDialoget closeDialogles fonctions ;
  2. el-dialogConfiguration d'événement prise en charge ;
  3. Prend en charge la configuration des attributs de composant par défautslot ;
  4. Prend en charge el-dialogd'autres configurations de slot, telles que headeret footeretc.;
  5. Le lancement d'événements spécifiques dans des composants de contenu prend en charge la fermeture de la boîte de dialogue ;
  6. Le contenu d'affichage pris en charge est jsx, 普通文本,Vue Component ;
  7. Prend en charge les fonctions de rappel qui contrôlent si le contenu affiché peut être fermé, par exemplebeforeClose ;
  8. Prend en charge l'affichage avant les crochets, par exempleonBeforeOpen ;
  9. Prend en charge la modification des propriétés de configuration lors de la définition et de la fenêtre contextuelle ;
  10. Prend en charge l'héritage du prototype de root vue, vous pouvez utiliser des fonctions telles vue-i18nque ;$t
  11. Invite de paramètres de prise en chargets ;

(1) Préparer useDialog.tsla 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 useDialogles 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-dialogd'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 ;openDialogcloseDialog
el-dialog
slot
el-dialogheaderfooter
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 ;

  1. pris en charge dans la définitioncloseEventName ;
interface Options<P> {
  // ...
  closeEventName?: string // 新增的属性
}
  1. Modifiez useDialogla fonction pour recevoir closeEventNamel'é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 exemple onBeforeOpen;

  1. Il est pris en charge dans la définition onBeforeOpenet beforeCloseDialogtransmis au composant de contenu par défaut, avec les paramètres d'appel du composant ;
type DialogProps = ElDialogInstance['$props'] & {
  onBeforeOpen?: () => boolean | void
}
  1. Modifiez useDialogla fonction pour recevoir onBeforeOpenl'événement et le transmettre 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) 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 telles vue-i18nque ;$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 openDialogla 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 useDialogla modifier dans Hook, sans avoir à la modifier une par une.

4. Pratique de cas UseDialog Hook

Ensuite, nous utilisons useDialogHook pour résoudre le problème d'achat d'application mentionné au début.

(1) Créer components/buy.vueun composant d'achat

<script lang="ts" setup>
  const props = defineProps({
    from: {
      type: String,
      default: '',
    },
  })
</script>
<template>
  我是购买组件
</template>

(2) pages/subscription.vueUtilisez buy.vuele 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& closeEventNameExemple :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 useDialogde l’encapsulation Hook el-dialogpeut 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.

Bienvenue pour suivre le compte LigaAI, et nous avons hâte que vous cliquiez sur la plateforme de collaboration R&D intelligente nouvelle génération pour avoir plus d'échanges avec nous.

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
{{o.name}}
{{m.nom}}

Je suppose que tu aimes

Origine my.oschina.net/u/5057806/blog/11091317
conseillé
Classement