Compartilhamento de tecnologia | Como usar o Hook para encapsular el-dialog no desenvolvimento de janelas pop-up?

Janelas pop-up são um requisito comum no desenvolvimento front-end. Os componentes da estrutura Element UI el-dialogfornecem funções básicas relacionadas às janelas pop-up. No entanto, no desenvolvimento real, inevitavelmente encontraremos alguns requisitos personalizados, como encapsulamento secundário de janelas pop-up para gerenciar uniformemente estilos e comportamentos no projeto.

Este artigo compartilhará como usar useDialogo encapsulamento Hook el-dialogpara obter um componente pop-up mais flexível e fácil de usar.

1. Esclarecimento de questões

“Aplicar um componente comum a múltiplas páginas” é um cenário prático muito comum.

Por exemplo: Tomemos como exemplo a compra de um aplicativo. O usuário pode fazer uma compra na página de pagamento ou pode acionar uma solicitação de compra enquanto navega em outras páginas. para concluir o comportamento de compra.

Para alcançar essa funcionalidade, as seguintes etapas normalmente foram executadas no passado:

  1. Encapsule o componente de compra : primeiro crie um componente de compra geral para que possa ser reutilizado em diferentes páginas e cenários.
  2. Renderize o componente de compra na página de pagamento : incorpore o componente de compra diretamente na página de pagamento.
  3. Use el-dialogo componente de compra de exibiçãoel-dialog em outras páginas: controle a exibição do componente em outras páginas e use visiblevariáveis ​​​​de estado (geralmente uma refvariável responsiva) para controlar dinamicamente o pop-up e o fechamento da caixa de diálogo.

Embora este método possa atender aos requisitos funcionais, à medida que o componente é usado por mais e mais páginas e funções, a manutenção se tornará mais complexa e complicada - para cada página adicional de uso, a lógica para controlar a exibição/ocultação deve ser escrita repetidamente no código.

Então, existe uma maneira melhor de simplificar esse processo? É possível utilizar uma função separada para controlar globalmente a abertura e o fechamento do componente de compra de alguma forma, reduzindo assim a duplicação de código e os custos de manutenção?

2. Sobre useDialog Gancho

No Vue, os Hooks permitem "conectar" recursos do Vue a componentes funcionais ou APIs. Eles geralmente são usados ​​na API Composition, que é um conjunto de funções lógicas responsivas e reutilizáveis ​​fornecidas pelo Vue.

O Hook mencionado neste artigo useDialogé um Hook personalizado que encapsula el-dialogas funções básicas do componente. Ele também pode fornecer recursos adicionais para gerenciar e exibir janelas pop-up no projeto.

3. Implementar o gancho useDialog

useDialogHook precisa atingir os seguintes objetivos:

  1. Conheça o uso básico, el-dialogos atributos básicos passados ​​e o conteúdo exibido pelo slot padrão, exportação openDialoge closeDialogfunções;
  2. el-dialogConfiguração de eventos suportada ;
  3. Suporta slotconfiguração de atributos de componentes padrão;
  4. Suporta el-dialogoutras configurações de slot, como headere footeretc.;
  5. Lançar eventos específicos em componentes de conteúdo oferece suporte ao fechamento do diálogo;
  6. O conteúdo de exibição suportado é jsx, 普通文本, Vue Component;
  7. Suporta funções de retorno de chamada que controlam se o conteúdo exibido pode ser fechado, por exemplo beforeClose;
  8. Suporta exibição antes de ganchos, por exemplo onBeforeOpen;
  9. Suporta modificação de propriedades de configuração durante definição e pop-up;
  10. Suporta a herança do protótipo do root vue, você pode usar funções vue-i18ncomo ;$t
  11. Prompt de parâmetro de suporte ts;

(1) Preparar useDialog.tsa definição do tipo de implementação de arquivo

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 useDialogfunções comuns

As funções a seguir implementam o uso básico, incluindo as metas 1, 2, 3, 4, 6 e 11.

Meta 1: Atender ao uso básico, passar atributos el-dialogbásicos e conteúdo de exibição de slot padrão , exportação openDialoge closeDialogfunções ; etc.; Meta 6: Suportar conteúdo de exibição de , , ; Meta 11: Suportar prompts de parâmetros;
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) Alcançar a Meta 5

Meta 5: Lançar eventos específicos em componentes de conteúdo para apoiar o fechamento do diálogo;

  1. apoiado na definição closeEventName;
interface Options<P> {
  // ...
  closeEventName?: string // 新增的属性
}
  1. Modifique useDialoga função para receber closeEventNameo evento para fechar o 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) Alcançar as Metas 7 e 8

Meta 7: Apoiar uma função de retorno de chamada que controle se ela pode ser fechada no conteúdo exibido, por exemplo beforeClose;
Meta 8: Apoiar ganchos antes de exibir, por exemplo onBeforeOpen;

  1. É suportado na definição onBeforeOpene beforeCloseDialogpassado para o componente de conteúdo por padrão, com configurações de chamada de componente;
type DialogProps = ElDialogInstance['$props'] & {
  onBeforeOpen?: () => boolean | void
}
  1. Modifique useDialoga função para receber onBeforeOpeno evento e repassá-lo 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) Alcançar as metas 9 e 10

Meta 9: Suportar a modificação de propriedades de configuração ao definir e aparecer;
Meta 10: Suportar a herança do protótipo do vue raiz, você pode usar funções 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 }
}

Depois de usar o Hook através do pacote acima useDialog, quando precisar abrir uma janela, basta introduzir o Hook e chamar openDialogo método, que é muito conveniente e conciso. Além disso, esse encapsulamento também tornará mais conveniente modificar a lógica da janela pop-up posteriormente. Você só precisa modificá- useDialogla no Hook, sem precisar editá-la uma por uma.

4. Prática de caso UseDialog Hook

A seguir, usamos useDialogo Hook para resolver o problema de compra do aplicativo mencionado no início.

(1) Criar components/buy.vuecomponente de compra

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

(2) pages/subscription.vueUse buy.vueo componente de compra na página

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

  <Buy from="subscription" />

</template>

buy.vue(3) Componentes de compra pop-up em outras páginas de funções

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

Extensão: outras aplicações do useDialog Hook

beforeClose& closeEventNameExemplo: 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

O uso useDialogdo encapsulamento Hook el-dialogpode tornar a tecnologia front-end mais interessante e concisa. O autor também espera que todos possam experimentar esse tipo de método de encapsulamento para tornar o código front-end mais elegante e fácil de manter.

Excelentes engenheiros são como excelentes chefs. Eles dominam habilidades culinárias e de tempero requintadas para tornar cada prato delicioso!


A LigaAI atribui grande importância à manutenção e construção da cultura do desenvolvedor e continuará a compartilhar mais compartilhamento de tecnologia e práticas tecnológicas interessantes.

Bem-vindo a seguir a conta LigaAI e esperamos que você clique na plataforma de colaboração inteligente de P&D de nova geração para ter mais intercâmbios conosco.

Para ajudar os desenvolvedores a navegar, a LigaAI espera viajar com você durante todo o caminho!

Um programador nascido na década de 1990 desenvolveu um software de portabilidade de vídeo e faturou mais de 7 milhões em menos de um ano. O final foi muito punitivo! Alunos do ensino médio criam sua própria linguagem de programação de código aberto como uma cerimônia de maioridade - comentários contundentes de internautas: Contando com RustDesk devido a fraude desenfreada, serviço doméstico Taobao (taobao.com) suspendeu serviços domésticos e reiniciou o trabalho de otimização de versão web Java 17 é a versão Java LTS mais comumente usada no mercado do Windows 10 Atingindo 70%, o Windows 11 continua a diminuir Open Source Daily | Google apoia Hongmeng para assumir o controle de telefones Android de código aberto apoiados pela ansiedade e ambição da Microsoft; Electric desliga a plataforma aberta Apple lança chip M4 Google exclui kernel universal do Android (ACK) Suporte para arquitetura RISC-V Yunfeng renunciou ao Alibaba e planeja produzir jogos independentes na plataforma Windows no futuro
{{o.nome}}
{{m.nome}}

Acho que você gosta

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