如何使用JS函数式调用方式将自定义组件并以弹窗形式展示?

在开发过程中,经常会用到弹窗,而有的弹窗可能是临时性的,其实不太想在template中创建一个临时性的弹窗组件,那么有没有比较好的方案处理呢?比如以函数式方式调用方式,其实是有的;

elementUI 2.x为例,MessageBox组件模拟了系统的alert,confirmprompt。其中prompt允许我们填入一个文本元素并给出了示例。

但有时候,我们并不总是需要文本输入,假设我们需要一个临时的时间选择弹窗怎么处理呢?

按照之前的我可能会无脑用Dialog组件写一个弹窗,里面放一个日期组件,但是这样会无形创造很多变量在datamethods

MessageBox组件给我们提供了一个函数式方式调用弹窗的方式,好处在于内容可以是VNode形式。贴出官网的调用方式

html 复制代码
<template>
  <el-button type="text" @click="open">点击打开 Message Box</el-button>
</template>

<script>
  export default {
    methods: {
      open() {
        const h = this.$createElement;
        this.$msgbox({
          title: '消息',
          message: h('p', null, [
            h('span', null, '内容可以是 '),
            h('i', { style: 'color: teal' }, 'VNode')
          ]),
          showCancelButton: true,
          confirmButtonText: '确定',
          cancelButtonText: '取消',
          beforeClose: (action, instance, done) => {
            if (action === 'confirm') {
              instance.confirmButtonLoading = true;
              instance.confirmButtonText = '执行中...';
              setTimeout(() => {
                done();
                setTimeout(() => {
                  instance.confirmButtonLoading = false;
                }, 300);
              }, 3000);
            } else {
              done();
            }
          }
        }).then(action => {
          this.$message({
            type: 'info',
            message: 'action: ' + action
          });
        });
      }
    }
  }

其中message接收VNode参数,官网是通过Vue实例上提供的$createElement方法动态创建的,这种数据结构和webpack.vue文件打包后的数据结构是一致的。

Vue官网对其有说明: 渲染函数 & JSX

官网说createElement函数的第一个参数支持一个 HTML 标签名、组件选项对象,或者resolve 了上述任何一种的一个 async 函数。

既然这样,那我直接把组件el-date-picker扔进去不就好了吗。

csharp 复制代码
message: h('el-date-picker', null),

遗憾的是,弹窗可以正确的渲染出日期组件,由于该组件内部的原因,该组件在选择日期时出现了问题,不能正确执行。

经过我多种尝试,有一种方式可以实现我想要的效果。这里要用到平时开发用的比较少的方法,Vue.extendVue.compile

Vue.extend的用法

Vue.extend是用于创建一个"扩展实例构造器"的方法,可以基于基础Vue构造器来创建一个"子类"。通过Vue.extend返回的是一个组件构造器,可以用来挂载一个新的Vue实例。

js 复制代码
// 创建构造器
const CustomVue = Vue.extend({
  // 扩展选项
})

// 挂载实例
const vm = new CustomVue().$mount('#app')

或者手动挂载
const customVue = new CustomVue().$mount();
document.body.append(customVue.$el);

Vue.extend主要有以下几种使用场景:

  1. 创建可复用组件 :通过Vue.extend创建一个组件构造器,然后在其他组件中通过components选项注册和使用。
  2. 扩展现有组件:基于某个已有的组件构造器创建一个新的构造器,从而实现组件的扩展和继承。
  3. 动态渲染组件 :在运行时动态创建并渲染一个组件,比如通过Vue.extend创建一个组件构造器,然后通过new实例化并挂载。

Vue.compile的用法

Vue.compile是用于编译模板字符串的方法。它返回一个渲染函数render和静态节点的staticRenderFns。

js 复制代码
const { render, staticRenderFns } = Vue.compile(template)

Vue.compile 方法支持一个参数,即模板字符串。它会将模板字符串编译成一个 render 函数,并返回一个对象,包含 render 函数和静态节点的 staticRenderFns。些静态节点可以在组件渲染时进行缓存,提高渲染性。Vue.compile主要用于以下场景:

  1. 运行时编译模板字符串:当使用运行时构建的Vue时,模板需要在运行时编译,可以使用Vue.compile编译模板字符串。
  2. 扩展Vue编译器:Vue.compile暴露了编译模板的内部实现,可以基于此扩展编译器的功能,比如添加自定义指令等。

结合这两者的功能,我们就不用自己手动去编写VNode结构体了。

开发环境配置

默认我们在开发时,import Vue from 'vue';引入的只有运行时,想要使用的完整功能的话,需要在webpack中配置,引入带有编译引擎的版本,在vue.config.js文件中配置如下:

css 复制代码
configureWebpack: {
    resolve: {
        alias: {
            vue$: 'vue/dist/vue.esm.js',
        },
    },
},

根据自己的vuecli版本看具体怎么配置。

实战案例

下面我贴出示例代码参考

html 复制代码
<template>
	<div>
		<el-button @click="openModal" type="primary">打开弹窗</el-button>
	</div>
</template>
<script>
	import Vue from 'vue';
	export default {
		name: 'modal',
		components: {},
		props: {},
		data() {
			return {
				obj: {
					date: new Date()
				}
			};
		},
		computed: {},
		created() {},
		mounted() {},
		destroyed() {},
		methods: {
			openModal() {
				let pickerTime = new Date(); //定义一个变量用于存储选择的时间,先赋一个默认值
				const createElement = this.$createElement;
                                //手动创建一个Wrap组件,内容包含日期选择组件
				const Wrap = Vue.extend({
					template: `<el-date-picker placeholder="选择日期时间" type="datetime" v-model="value" @change="onChange"></el-date-picker>`,
					data() {
						return {
							value: pickerTime
						};
					},
					methods: {
						onChange(evt) {
							pickerTime = evt; //将组件内部的值赋值到外部变量
						}
					}
				});
				this.$options.components['wrap'] = Wrap; //将Wrap组件挂载到当前页面的局部组件中
				//对Wrap组件进行编译会返回一个包含render函数和静态函数staticRenderFns
				var compiled = Vue.compile(`<wrap/>`);
                                
                                
                           //唤起弹窗
				this.$msgbox({
					title: '消息',
					message: compiled.render.call(this, createElement), //调用编辑后组件的render函数,转化为虚拟dom
					showCancelButton: true,
					confirmButtonText: '确定',
					cancelButtonText: '取消',
					beforeClose: (action, instance, done) => {
						if (action === 'confirm') {
							instance.confirmButtonLoading = true;
							instance.confirmButtonText = '执行中...';
							setTimeout(() => {
								done();
								setTimeout(() => {
									console.log(pickerTime); //打印选择的时间
									instance.confirmButtonLoading = false;
								}, 300);
							}, 3000);
						} else {
							done();
						}
					}
				}).then(action => {
					this.$message({
						type: 'info',
						message: 'action: ' + action
					});
				});
			}
		}
	};
</script>
<style lang="scss" scoped></style>
<style scoped></style>

依此类推,我们就可以实现很多简单功能的交互弹窗了。

当然,如果上面的不满足的话,我们完全可以自己定制一个属于自己的函数式弹窗组件,下面列出示例代码:

html 复制代码
<template>
	<div>
		<el-button @click="openModal" type="primary">打开自定义弹窗</el-button>
	</div>
</template>
<script>
	import Vue from 'vue';
	export default {
		name: 'modal',
		components: {},
		props: {},
		data() {
			return {};
		},
		computed: {},
		created() {},
		mounted() {},
		destroyed() {},
		methods: {
			//自定义弹窗
			openModal() {
				// 定义一个扩展的 Vue 构造器
				var ModalComponent = Vue.extend({
					template: `
							<div v-show="isVisible" class="modal">
							<div class="modal-content">
								<div class="modal-header">
								<span class="close" @click="close">&times;</span>
								<h2>{{ title }}</h2>
								</div>
								<div class="modal-body">
								<p>{{ content }}</p>
								</div>
								<div class="modal-footer">
								<button @click="close">关闭</button>
								</div>
							</div>
							</div>
						`,
					props: ['title', 'content'],
					data: function () {
						return {
							isVisible: true, // 默认显示
						};
					},
					methods: {
						open: function () {
							this.isVisible = true;
						},
						close: function () {
							this.isVisible = false;
							this.$nextTick(() => {
								// 确保DOM已经更新
								if (this.$el && this.$el.parentNode) {
									this.$el.parentNode.removeChild(this.$el);
								}
								// 清理组件实例
								this.$destroy();
							});
						},
					},
				});
				// 使用 ModalComponent 创建 modal 实例
				var modalInstance = new ModalComponent({
					propsData: {
						title: 'Vue Modal',
						content: '这是一个使用 Vue.extend 创建的模态框。',
					},
				});

				// 挂载到新创建的 div 元素上,并添加到 body 中
				var mountNode = document.createElement('div');
				document.body.appendChild(mountNode);
				modalInstance.$mount(mountNode);
			},
		},
	};
</script>
<style lang="scss" scoped></style>
<style>
	.modal {
		position: fixed;
		z-index: 1000;
		left: 0;
		top: 0;
		width: 100%;
		height: 100%;
		overflow: auto;
		background-color: rgba(0, 0, 0, 0.4);
	}

	.modal-content {
		background-color: #fefefe;
		margin: 15% auto;
		padding: 20px;
		border: 1px solid #888;
		width: 80%;
	}

	.close {
		color: #aaa;
		float: right;
		font-size: 28px;
		font-weight: bold;
	}

	.close:hover,
	.close:focus {
		color: black;
		text-decoration: none;
		cursor: pointer;
	}

	.modal-header {
		padding-bottom: 10px;
		border-bottom: 1px solid #e5e5e5;
	}

	.modal-body {
		margin-top: 10px;
	}

	.modal-footer {
		padding-top: 10px;
		border-top: 1px solid #e5e5e5;
	}
</style>

创建实例时,传递参数使用了propsData,这是点击API:propsData查看

后面有好的想法再继续补充...

相关推荐
崔庆才丨静觅1 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60612 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了2 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅2 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅3 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅3 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment3 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅3 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊3 小时前
jwt介绍
前端
爱敲代码的小鱼3 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax