在开发过程中,经常会用到弹窗,而有的弹窗可能是临时性的,其实不太想在template
中创建一个临时性的弹窗组件,那么有没有比较好的方案处理呢?比如以函数式方式调用方式,其实是有的;
以elementUI 2.x
为例,MessageBox
组件模拟了系统的alert
,confirm
和prompt
。其中prompt
允许我们填入一个文本元素并给出了示例。
但有时候,我们并不总是需要文本输入,假设我们需要一个临时的时间选择弹窗怎么处理呢?
按照之前的我可能会无脑用Dialog
组件写一个弹窗,里面放一个日期组件,但是这样会无形创造很多变量在data
和methods
中
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.extend
和Vue.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主要有以下几种使用场景:
- 创建可复用组件 :通过
Vue.extend
创建一个组件构造器,然后在其他组件中通过components
选项注册和使用。 - 扩展现有组件:基于某个已有的组件构造器创建一个新的构造器,从而实现组件的扩展和继承。
- 动态渲染组件 :在运行时动态创建并渲染一个组件,比如通过
Vue.extend
创建一个组件构造器,然后通过new实例化并挂载。
Vue.compile的用法
Vue.compile是用于编译模板字符串的方法。它返回一个渲染函数render和静态节点的staticRenderFns。
js
const { render, staticRenderFns } = Vue.compile(template)
Vue.compile 方法支持一个参数,即模板字符串。它会将模板字符串编译成一个 render 函数,并返回一个对象,包含 render 函数和静态节点的 staticRenderFns。些静态节点可以在组件渲染时进行缓存,提高渲染性。Vue.compile主要用于以下场景:
- 运行时编译模板字符串:当使用运行时构建的Vue时,模板需要在运行时编译,可以使用Vue.compile编译模板字符串。
- 扩展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">×</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查看
后面有好的想法再继续补充...