Compartir tecnología | ¿Cómo utilizar Hook para encapsular el-dialog en el desarrollo de ventanas emergentes?

Las ventanas emergentes son un requisito común en el desarrollo front-end. Los componentes en el marco de Element UI el-dialogproporcionan funciones básicas relacionadas con las ventanas emergentes. Sin embargo, en el desarrollo real, inevitablemente encontraremos algunos requisitos personalizados, como la encapsulación secundaria de ventanas emergentes para administrar de manera uniforme los estilos y comportamientos en el proyecto.

Este artículo compartirá cómo utilizar useDialogla encapsulación Hook el-dialogpara lograr un componente emergente más flexible y fácil de usar.

1. Aclaración de cuestiones

"Aplicar un componente común a varias páginas" es un escenario práctico muy común.

Por ejemplo: tome la compra de una aplicación como ejemplo. El usuario puede realizar una compra en la página de pago o puede activar una solicitud de compra mientras navega por otras páginas. En este caso, es necesario que aparezca un cuadro de diálogo para guiar al usuario. para completar el comportamiento de compra.

Para lograr esta funcionalidad, normalmente se han seguido los siguientes pasos en el pasado:

  1. Encapsule el componente de compra : primero cree un componente de compra general para que pueda reutilizarse en diferentes páginas y escenarios.
  2. Representar el componente de compra en la página de pago : incruste el componente de compra directamente en la página de pago.
  3. Utilice el-dialogel componente de compra de visualizaciónel-dialog en otras páginas: controle la visualización del componente en otras páginas y utilice visiblevariables de estado (generalmente una refvariable de respuesta) para controlar dinámicamente la ventana emergente y el cierre del cuadro de diálogo.

Aunque este método puede cumplir con los requisitos funcionales, a medida que el componente es utilizado por más y más páginas y funciones, el mantenimiento se volverá más complejo y engorroso: para cada página adicional de uso, la lógica para controlar la visualización/ocultación debe escribirse repetidamente en código.

Entonces, ¿existe una mejor manera de simplificar este proceso? ¿Es posible utilizar una función separada para controlar globalmente la apertura y el cierre del componente de compra de alguna manera, reduciendo así la duplicación de código y los costos de mantenimiento?

2. Acerca del usoDialog Hook

En Vue, los Hooks permiten "enganchar" características de Vue en componentes funcionales o API. Por lo general, se usan en la API de composición, que es un conjunto de funciones lógicas receptivas y reutilizables proporcionadas por Vue.

El Hook mencionado en este artículo useDialoges un Hook personalizado que encapsula el-dialoglas funciones básicas del componente. También puede proporcionar funciones adicionales para administrar y mostrar ventanas emergentes en el proyecto.

3. Implementar useDialog Hook

useDialogHook necesita lograr los siguientes objetivos:

  1. Conozca el uso básico, el-dialoglos atributos básicos pasados ​​y el contenido mostrado por la ranura, exportación openDialogy closeDialogfunciones predeterminadas;
  2. el-dialogConfiguración de eventos admitida ;
  3. Admite slotla configuración de atributos de componentes predeterminados;
  4. Admite el-dialogotras configuraciones de ranura, como headery footeretc.;
  5. Lanzar eventos específicos en componentes de contenido permite cerrar el diálogo;
  6. El contenido de visualización admitido es jsx, 普通文本, Vue Component;
  7. Admite funciones de devolución de llamada que controlan si el contenido mostrado se puede cerrar, por ejemplo beforeClose;
  8. Admite visualización antes de los ganchos, por ejemplo onBeforeOpen;
  9. Admite la modificación de propiedades de configuración durante la definición y la ventana emergente;
  10. Admite heredar el prototipo de root vue, puede utilizar funciones vue-i18ncomo ;$t
  11. Aviso de parámetro de soporte ts;

(1) Preparar useDialog.tsla definición del tipo de implementación del archivo

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 useDialogfunciones ordinarias

Las siguientes funciones implementan el uso básico, incluidos los objetivos 1, 2, 3, 4, 6 y 11.

Objetivo 1: cumplir con el uso básico, transferir el-dialogatributos básicos y contenido, exportación openDialogy closeDialogfunciones de visualización de ranuras predeterminadas.
Meta 2: admitir el-dialogla configuración de eventos. Meta 3
.: admitir la configuración de atributos de los slotcomponentes predeterminados; etc. Objetivo 6: Soporte de visualización de contenido de , , ; Objetivo 11: Soporte de indicaciones de parámetros;
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) Lograr el Objetivo 5

Objetivo 5: lanzar eventos específicos en componentes de contenido para permitir el cierre del diálogo;

  1. apoyado en la definición closeEventName;
interface Options<P> {
  // ...
  closeEventName?: string // 新增的属性
}
  1. Modifique useDialogla función para recibir closeEventNameel evento para cerrar el 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) Lograr los Objetivos 7 y 8

Objetivo 7: admitir una función de devolución de llamada que controle si se puede cerrar en el contenido mostrado, por ejemplo beforeCloseObjetivo
8: admitir ganchos antes de mostrar, por ejemplo onBeforeOpen;

  1. Se admite en la definición onBeforeOpeny beforeCloseDialogse pasa al componente de contenido de forma predeterminada, con la configuración de llamada del componente;
type DialogProps = ElDialogInstance['$props'] & {
  onBeforeOpen?: () => boolean | void
}
  1. Modifique useDialogla función para recibir onBeforeOpenel evento y transmitirlo 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) Lograr los objetivos 9 y 10

Objetivo 9: admitir la modificación de propiedades de configuración al definir y abrir.
Objetivo 10: admitir la herencia del prototipo de root vue, puede utilizar funciones vue-i18ncomo ;$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 }
}

Después de usar Hook a través del paquete anterior useDialog, cuando se necesita una ventana emergente, solo necesita introducir Hook y llamar openDialogal método, lo cual es muy conveniente y conciso. Además, dicha encapsulación también hará que sea más conveniente modificar la lógica de la ventana emergente más adelante. Solo necesita modificarla useDialogen Hook, sin tener que editarla una por una.

4. Práctica de casos de UseDialog Hook

A continuación, utilizamos useDialogHook para solucionar el problema de compra de la aplicación mencionado al principio.

(1) Crear components/buy.vuecomponente de compra

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

(2) pages/subscription.vueUtilice buy.vueel componente de compra en la página.

<script lang="ts" setup>
  import Buy from '@/components/buy.vue'
</script>
<template>

  <Buy from="subscription" />

</template>

buy.vue(3) Componentes de compra emergentes en otras páginas de funciones

<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>

Extensión: otras aplicaciones de usoDialog Hook

beforeClose& closeEventNameEjemplo: buy.vuecomprar 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

El uso de useDialogla encapsulación Hook el-dialogpuede hacer que la tecnología front-end sea más interesante y concisa. El autor también espera que todos puedan probar este tipo de método de encapsulación para hacer que el código front-end sea más elegante y fácil de mantener.

Los excelentes ingenieros son como excelentes chefs. ¡Dominan exquisitas habilidades culinarias y condimentadoras para hacer que cada plato sea delicioso!


LigaAI concede gran importancia al mantenimiento y construcción de la cultura de los desarrolladores y continuará compartiendo más tecnología y prácticas tecnológicas interesantes.

Bienvenido a seguir la cuenta de LigaAI y esperamos que haga clic en la plataforma de colaboración inteligente de I+D de nueva generación para tener más intercambios con nosotros.

Para ayudar a los desarrolladores a zarpar, ¡LigaAI espera viajar contigo durante todo el camino!

Un programador nacido en los años 90 desarrolló un software de portabilidad de vídeo y ganó más de 7 millones en menos de un año. ¡El final fue muy duro! Los estudiantes de secundaria crean su propio lenguaje de programación de código abierto como una ceremonia de mayoría de edad: comentarios agudos de los internautas: debido al fraude desenfrenado, confiando en RustDesk, el servicio doméstico Taobao (taobao.com) suspendió los servicios domésticos y reinició el trabajo de optimización de la versión web Java 17 es la versión Java LTS más utilizada. Cuota de mercado de Windows 10. Alcanzando el 70%, Windows 11 continúa disminuyendo. Open Source Daily | Google apoya a Hongmeng para hacerse cargo de los teléfonos Android de código abierto respaldados por Docker; Electric cierra la plataforma abierta Apple lanza el chip M4 Google elimina el kernel universal de Android (ACK) Soporte para la arquitectura RISC-V Yunfeng renunció a Alibaba y planea producir juegos independientes en la plataforma Windows en el futuro
{{o.nombre}}
{{m.nombre}}

Supongo que te gusta

Origin my.oschina.net/u/5057806/blog/11091317
Recomendado
Clasificación