百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术教程 > 正文

极致舒适的Vue弹窗使用方案

toqiye 2025-02-09 14:17 23 浏览 0 评论

一个Hook让你体验极致舒适的Dialog使用方式!

Dialog地狱

为啥是地狱?

因为凡是有Dialog出现的页面,其代码绝对优雅不起来!因为一旦你在也个组件中引入Dialog,就最少需要额外维护一个visible变量。如果只是额外维护一个变量这也不是不能接受,可是当同样的Dialog组件,即需要在父组件控制它的展示与隐藏,又需要在子组件中控制。

为了演示我们先实现一个MyDialog组件,代码来自ElementPlus的Dialog示例

html复制代码


演示场景

就像下面这样:

示例代码如下:

html复制代码


这里的MyDialog会被父组件和两个Comp组件都会触发,如果父组件并不关心子组件的onSubmit事件,那么这里的submit在父组件里唯一的作用就是处理Dialog的展示!!!这样真的好吗?不好!

来分析一下,到底哪里不好!

MyDialog本来是submit动作的后续动作,所以理论上应该将MyDialog写在Comp组件中。但是这里为了管理方便,将MyDialog挂在父组件上,子组件通过事件来控制MyDialog。

再者,这里的handleComp1Dialog和handleComp2Dialog函数除了处理MyDialog外,对于父组件完全没有意义却写在父组件里。

如果这里的Dialog多的情况下,简直就是Dialog地狱啊!

理想的父组件代码应该是这样:

html复制代码


在函数中处理弹窗的相关逻辑才更合理。

解决之道

朕观之,是书之文或不雅,致使人之心有所厌,何得无妙方可解决?

依史记之辞曰:“天下苦Dialog久矣,苦楚深深,望有解脱之道。”于是,诸位贤哲纷纷举起讨伐Dialog之旌旗,终“命令式Dialog”逐渐突破困境之境地。

没错现在网上对于Dialog的困境,给出的解决方案基本上就“命令式Dialog”看起来比较优雅!这里给出几个网上现有的命令式Dialog实现。

命令式一

吐槽一下~,这种是能在函数中处理弹窗逻辑,但是缺点是MyDialog组件与showMyDialog是两个文件,增加了维护的成本。

命令式二

基于第一种实现的问题,不就是想让MyDialog.vue和.js文件合体吗?于是诸位贤者想到了JSX。于是进一步的实现是这样:

嗯,这下完美了!

完美?还是要吐槽一下~

  • 如果我的系统中有很多弹窗,难道要给每个弹窗都写成这样吗?
  • 这种兼容JSX的方式,需要引入支持JSX的依赖!
  • 如果工程中不想即用template又用JSX呢?
  • 如果已经存在使用template的弹窗了,难道推翻重写吗?
  • ...

思考

首先承认一点命令式的封装的确可以解决问题,但是现在的封装都存一定的槽点。

如果有一种方式,即保持原来对话框的编写方式不变,又不需要关心JSX和template的问题,还保存了命令式封装的特点。这样是不是就完美了?

那真的可以同时做到这些吗?

如果存在一个这样的Hook可以将状态驱动的Dialog,转换为命令式的Dialog吗,那不就行了?

它来了:useCommandComponent

父组件这样写:

html复制代码


Comp组件这样写:

html复制代码


对于MyDialog无需任何改变,保持原来的样子就可以了!

useCommandComponent真的做到了,即保持原来组件的编写方式,又可以实现命令式调用

使用效果:

是不是感受到了莫名的舒适?

不过别急,要想体验这种极致的舒适,你的Dialog还需要遵循两个约定!

两个约定

如果想要极致舒适的使用useCommandComponent,那么弹窗组件的编写就需要遵循一些约定(其实这些约定应该是弹窗组件的最佳实践)。

约定如下:

  • 弹窗组件的props需要有一个名为visible的属性,用于驱动弹窗的打开和关闭。
  • 弹窗组件需要emit一个close事件,用于弹窗关闭时处理命令式弹窗。

如果你的弹窗组件满足上面两个约定,那么就可以通过useCommandComponent极致舒适的使用了!!

这两项约定虽然不是强制的,但是这确实是最佳实践!不信你去翻所有的UI框看看他们的实现。我一直认为学习和生产中多学习优秀框架的实现思路很重要!

如果不遵循约定

这时候有的同学可能会说:哎嘿,我就不遵循这两项约定呢?我的弹窗就是要标新立异的不用visible属性来控制打开和关闭,我起名为dialogVisible呢?我的弹窗就是没有close事件呢?我的事件是具有业务意义的submit、cancel呢?...

得得得,如果真的没有遵循上面的两个约定,依然可以舒适的使用useCommandComponent,只不过在我看来没那么极致舒适!虽然不是极致舒适,但也要比其他方案舒适的多!

如果你的弹窗真的没有遵循“两个约定”,那么你可以试试这样做:

html复制代码


如上,只需要在调用myDialog函数时在props中将驱动弹窗的状态设置为true,在需要关闭弹窗的事件中调用myDialog.close()即可!

这样是不是看着虽然没有上面的极致舒适,但是也还是挺舒适的?

源码与实现

实现思路

对于useCommandComponent的实现思路,依然是命令式封装。相比于上面的那两个实现方式,useCommandComponent是将组件作为参数传入,这样保持组件的编写习惯不变。并且useCommandComponent遵循单一职责原则,只做好组件的挂载和卸载工作,提供足够的兼容性

其实useCommandComponent有点像React中的高阶组件的概念

源码

源码不长,也很好理解!在实现useCommandComponent的时候参考了ElementPlus的MessageBox。

源码如下:

ts复制代码import { AppContext, Component, ComponentPublicInstance, createVNode, getCurrentInstance, render, VNode } from 'vue';

export interface Options {
  visible?: boolean;
  onClose?: () => void;
  appendTo?: HTMLElement | string;
  [key: string]: unknown;
}

export interface CommandComponent {
  (options: Options): VNode;
  close: () => void;
}

const getAppendToElement = (props: Options): HTMLElement => {
  let appendTo: HTMLElement | null = document.body;
  if (props.appendTo) {
    if (typeof props.appendTo === 'string') {
      appendTo = document.querySelector(props.appendTo);
    }
    if (props.appendTo instanceof HTMLElement) {
      appendTo = props.appendTo;
    }
    if (!(appendTo instanceof HTMLElement)) {
      appendTo = document.body;
    }
  }
  return appendTo;
};

const initInstance = (
  Component: T,
  props: Options,
  container: HTMLElement,
  appContext: AppContext | null = null
) => {
  const vNode = createVNode(Component, props);
  vNode.appContext = appContext;
  render(vNode, container);

  getAppendToElement(props).appendChild(container);
  return vNode;
};

export const useCommandComponent = (Component: T): CommandComponent => {
  const appContext = getCurrentInstance()?.appContext;

  const container = document.createElement('div');

  const close = () => {
    render(null, container);
    container.parentNode?.removeChild(container);
  };

  const CommandComponent = (options: Options): VNode => {
    if (!Reflect.has(options, 'visible')) {
      options.visible = true;
    }
    if (typeof options.onClose !== 'function') {
      options.onClose = close;
    } else {
      const originOnClose = options.onClose;
      options.onClose = () => {
        originOnClose();
        close();
      };
    }
    const vNode = initInstance(Component, options, container, appContext);
    const vm = vNode.component?.proxy as ComponentPublicInstance;
    for (const prop in options) {
      if (Reflect.has(options, prop) && !Reflect.has(vm.$props, prop)) {
        vm[prop as keyof ComponentPublicInstance] = options[prop];
      }
    }
    return vNode;
  };

  CommandComponent.close = close;

  return CommandComponent;
};

export default useCommandComponent;

除了命令式的封装外,我加入了const appContext = getCurrentInstance()?.appContext;。这样做的目的是,传入的组件在这里其实已经独立于应用的Vue上下文了。为了让组件依然保持和调用方相同的Vue上下文,我这里加入了获取上下文的操作!

基于这个情况,在使用useCommandComponent时需要保证它在setup中被调用,而不是在某个点击事件的处理函数中哦~

最后

如果你觉得useCommandComponent对你在开发中有所帮助,麻烦多点赞评论收藏

如果useCommandComponent对你实现某些业务有所启发,麻烦多点赞评论收藏

如果...,麻烦多点赞评论收藏

如果大家有其他弹窗方案,欢迎留言交流哦!

相关推荐

Star 17.3k!给它一张屏幕截图,即可一键克隆网页!

本文为大家分享一款本周爆火的GPT开源项目。前言你敢信,只凭借着一张屏幕截图即可转换生成HTML/TailwindCSS代码。可以算得上是前端工程师的福音。它就是screenshot-to-...

AI从截图直接生成代码、前端程序员的福利!

简介项目可以将任何屏幕截图或设计转换为干净的代码(支持大多数框架)。来自领先公司的开发人员和设计师使用的排名第一的工具。完全开源,在GitHub上拥有超过35,000颗星。非常受欢迎。各位小伙...

一款高颜值、跨平台、自托管的免费开源CRM项目——Twenty

前言大家好,这里是可爱的Cherry。作为一个“甲方”,Cherry其实挺知道客户管理的重要的。但是对于客户管理怎么做,以及CRM的作用,我却是一无所知。之前有朋友在评论区留言,说有没有开源的CRM系...

解放双手,前端界面再也不用自己写了?

随着AI技术的发展,现在有越来越多的尝试将AI应用于UI设计和开发中,以期提高效率和降低成本。今天就给大家介绍一个开源的AI网页生成工具:OpenUIOpenUIOpenUI是一个创...

代码调试,教给你(代码调试是什么意思)

昨天我和一些朋友一起调试代码,他们做程序员这一行都不太久,我向他们展示了一些代码调试技巧。今天早上我在想,我应该如何教授他们学习代码调试?我在Twitter上发了一条推文说,我从来没有见过任何好的调试...

Screenshot-to-code:用屏幕截图生成代码

Screenshot-to-code是一个简单的工具,可使用AI将屏幕截图、模型和Figma设计转换为干净、实用的代码。现在支持ClaudeSonnet3.5和GPT-4o!Scre...

next实现原理(next method)

Next.js是一个基于React的服务器端渲染(SSR)和静态生成(SSG)框架,它的实现原理涉及多个关键技术点,包括服务端渲染(SSR)、静态生成(SSG)、客户端渲染(CSR)、...

可逐步操作的具体流程(可逐步操作的具体流程包括)

假设你是一个单人开发者,使用主流技术栈(React+Node.js+MySQL),以下是详细步骤:---###**一、需求分析与原型设计**1.**核心功能清单**-用户能添加、删除、...

截图转代码只需1步!你离高效开发只差这款神器

引言在现代前端开发中,将设计稿转换为代码是一个既重要又耗时的环节。手动编写HTML结构、调试CSS样式、调整布局对齐,不仅耗费时间,还容易出错。而Screenshot-to-Code这款革...

web开发 前端 后端(web开发前端后端)

区别:1、前端是指用户可见的界面,而后端是指用户看不到的东西,考虑底层业务逻辑的实现,平台的稳定性、性能等。2、前端开发用到的技术有HTML5、CSS3、JS、jQuery、Bootstrap、Nod...

手把手教你Dify私有化部署,打造专属AI平台

一、Dify是什么?Dify是一款极具创新性的开源LLM应用开发平台,它就像是一把万能钥匙,为开发者们打开了通往生成式AI应用开发新世界的大门。其融合了后端即服务(BackendasS...

前后端分离架构设计:提升开发效率与业务支撑力的密钥

前后端分离架构设计解析一、定义与核心思想前后端分离是一种将用户界面(前端)与业务逻辑(后端)解耦的架构模式,通过RESTfulAPI或GraphQL实现数据交互。前端专注于视图渲染与交互逻辑...

Kubernetes最小部署单元Pod(kubernetes最小部署单元)

一、Kubernetes与Pod简介在当今云计算和容器化技术盛行的时代,Kubernetes已然成为容器编排领域的中流砥柱。它是一个开源的容器编排平台,由Google基于其内部使用的Bo...

【程序员必藏!零基础本地部署DeepSeek大模型保姆级教程】

为什么选择本地部署?数据安全:敏感代码/业务数据永不外传闪电响应:局域网推理延迟<100ms,告别云端排队深度定制:自由修改模型代码,打造专属AI助手准备工具(5分钟搞定)1核心工具下载...

【Python程序开发系列】使用Flask实现前后端分离(案例)

这是我的第398篇原创文章。一、引言随着web开发的不断发展,前后端分离已成为越来越流行的架构设计。Flask是一个轻量级的Pythonweb框架,非常适合用于构建API,然后配合前端框...

取消回复欢迎 发表评论: