如何使用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查看

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

相关推荐
Tiffany_Ho9 分钟前
【TypeScript】知识点梳理(三)
前端·typescript
安冬的码畜日常1 小时前
【D3.js in Action 3 精译_029】3.5 给 D3 条形图加注图表标签(上)
开发语言·前端·javascript·信息可视化·数据可视化·d3.js
太阳花ˉ1 小时前
html+css+js实现step进度条效果
javascript·css·html
小白学习日记2 小时前
【复习】HTML常用标签<table>
前端·html
程序员大金2 小时前
基于SpringBoot+Vue+MySQL的装修公司管理系统
vue.js·spring boot·mysql
john_hjy2 小时前
11. 异步编程
运维·服务器·javascript
风清扬_jd2 小时前
Chromium 中JavaScript Fetch API接口c++代码实现(二)
javascript·c++·chrome
丁总学Java3 小时前
微信小程序-npm支持-如何使用npm包
前端·微信小程序·npm·node.js
yanlele3 小时前
前瞻 - 盘点 ES2025 已经定稿的语法规范
前端·javascript·代码规范