懒加载对话框API封装技巧(项目必备技能,适用于各种需要懒加载动态展示的弹框组件)
懒加载对话框的方式做到了在你调用弹框展示API的那一刻,才去加载对话框文件并最终显示弹框效果。它最大的意义在于减少了你初次进入页面时加载的文件资源请求量和大小(加载资源小了,网页加载速度自然就提升了),将它封装成API顺带也做到了统一管理对话框的目的并且使得对话框文件和页面文件分离,起到了一个模块化的效果。
目前很多前端组件库提供的对话框组件在使用的时候,都是把对话框的代码和页面代码放在一起。例如下面Element官网的例子:

这样一来你的页面如果有不少对话框的话,整个代码看上去就会很杂乱,同时也增大了你当前页面的大小。有人会把对话框抽离出去,用import引进使用的页面。这样仅仅做到了代码的分离,当你页面加载的时候,这部分的弹框代码也会被一同加载,而不管是否用到了这个弹框。下面我们看几组代码示例和效果动态图来一起对比一下,下方对话框的主体代码摘自Element官网示例。
第一组(对话框代码和页面冗在一起):
测试页面代码如下:
<template>
<div>
<el-button type="text" @click="dialogVisible = true">点击打开 Dialog</el-button>
<el-dialog
title="提示"
:visible.sync="dialogVisible"
width="30%"
:before-close="handleClose">
<span>这是一段信息</span>
<span slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false">取 消</el-button>
<el-button type="primary" @click="dialogVisible = false">确 定</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
export default {
data() {
return {
dialogVisible: false
};
},
methods: {
handleClose(done) {
this.$confirm('确认关闭?')
.then(_ => {
done();
})
.catch(_ => {});
}
}
};
</script>
展示动态效果如下:

我们从响应报文里面看到文件的长度 Content-Length为23772B
第二组(对话框代码和页面代码分离,主体页面通过import引入对话框组件):
(下方仅做了最简单的处理,不建议照笔者所示应用于实际项目)
对话框组件代码如下:
<template>
<el-dialog
title="提示"
:visible.sync="dialogVisible"
width="30%"
:before-close="handleClose">
<span>这是一段信息</span>
<span slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false">取 消</el-button>
<el-button type="primary" @click="dialogVisible = false">确 定</el-button>
</span>
</el-dialog>
</template>
<script>
export default {
props: {
open: Boolean
},
watch: {
open: function (val) {
this.dialogVisible = val
}
},
data () {
return {
dialogVisible: false
}
},
methods: {
handleClose(done) {
this.$confirm('确认关闭?')
.then(_ => {
done();
})
.catch(_ => {});
}
},
mounted () {
this.dialogVisible = this.open
}
}
</script>
测试页面代码如下:
<template>
<div>
<el-button type="text" @click="dialogVisible = true">点击打开 Dialog</el-button>
<TestDialog :open="dialogVisible" />
</div>
</template>
<script>
import TestDialog from './test-dialog.vue'
export default {
components: {TestDialog},
data() {
return {
dialogVisible: false
}
}
};
</script>
展示动态效果如下:

我们从响应报文里面看到文件的长度 Content-Length为42495B, 代码分离之后最终的文件大小比第一组多了快一倍大小。
第三组(使用封装好的API唤起弹框):
弹框文件代码如下:
<template>
<el-dialog
title="提示"
:visible.sync="visible"
width="30%"
:before-close="handleClose">
<span>这是一段信息</span>
<span slot="footer" class="dialog-footer">
<el-button @click="onClose">取 消</el-button>
<el-button type="primary" @click="onCallback(true)">确 定</el-button>
</span>
</el-dialog>
</template>
<script>
export default {
data () {
return {}
},
methods: {
handleClose(done) {
this.$confirm('确认关闭?')
.then(_ => {
done();
})
.catch(_ => {});
}
}}
</script>
测试页面代码如下:
<template>
<div>
<el-button type="text" @click="openDialog">点击打开 Dialog</el-button>
</div>
</template>
<script>
export default {
methods: {
openDialog () {
this.$dialog.TestDialog()
}
}
};
</script>
展示动态效果如下:

通过控制台的network面板,我们可以直观的看到进入测试页面只加载了页面的文件(Content-Length为18710B),当我们唤起弹框的时候才会去请求弹框的文件(Content-Length为21983B),这说明我们封装的API起到了该有的效果。下面让我们一起来看一下该如何完成这个API的封装。
下面是该API的实现代码(文件名为dialog.js):
import {snake2Camel, camel2Snake} from '@/utils/String'
import Vue from 'vue'
// 获取需要动态创建的弹窗组件
const dialogsContext = require.context('./', true, /@([a-zA-Z\-0-9]+)\.vue$/, 'lazy')
const dialogs = dialogsContext.keys().reduce((views, key) => {
const fileName = key.match(/@([a-zA-Z\-0-9]+)\.vue$/i)[1]
if (!fileName) return views
let componentName = camel2Snake(fileName)
let clsName = snake2Camel(componentName)
return Object.assign(views, {[clsName]: key})
}, {})
async function createDialogAsync (path, data) {
let componentContext = await dialogsContext(path)
let temp = componentContext.default
return new Promise(function (resolve, reject) {
// 初始化配置参数
let opt = {
data
}
let component = Object.assign({}, temp)
// const parent = this
let initData = {
visible: true
}
Object.assign(initData, component.data())
opt.data && Object.assign(initData, JSON.parse(JSON.stringify(opt.data)))
component.data = function () {
return initData
}
// 创建构造器创建实例挂载
let DialogC = Vue.extend(component)
let dialog = new DialogC()
// 关闭事件
let _onClose = dialog.$options.methods.onClose
dialog.onClose = function () {
resolve()
dialog.$destroy()
_onClose && _onClose.call(dialog)
document.body.removeChild(dialog.$el)
}
// 回调事件
let _onCallback = dialog.$options.methods.onCallback
dialog.onCallback = function (...arg) {
try {
_onCallback && _onCallback()
resolve(...arg)
dialog.$destroy()
_onClose && _onClose.call(dialog)
document.body.removeChild(dialog.$el)
} catch (e) {
console.log(e)
}
}
dialog.$mount()
dialog.$watch('visible', function (n, o) {
dialog === false && dialog.onClose()
})
document.body.appendChild(dialog.$el)
})
}
function init (values) {
let dialogComponents = {}
if (!values) return
Object.keys(values).forEach((name) => {
dialogComponents[name] = function (data) {
return createDialogAsync.call(this, values[name], data)
}
})
return dialogComponents
}
Vue.prototype.$dialog = init(dialogs)
snake2Camel, camel2Snake是两个工具函数,用作字符串的格式转化,分别是蛇形转驼峰,驼峰转蛇形。代码如下:
function snake2Camel (str, capLower) {
let s = str.replace(/[-_](\w)/g, function (x) {
return x.slice(1).toUpperCase()
})
s = s.replace(/^\w/, function (x) {
return capLower ? x.toLowerCase() : x.toUpperCase()
})
return s
}
function camel2Snake (str) {
return str.replace(/([A-Z])/g, '-$1').toLowerCase()
}
重头戏来了,我们开始解析dialog.js的代码。 要完成懒加载文件需要借助require.context,不熟悉这个的朋友可以去找相关资料看看require的一些常用功能。require.context 的第三个入参决定了是否加载懒加载文件。拿到了文件之后,我们还需要初始化弹框,并把他显示出来。这里涉及到了Vue的两个API,extend和$mount的知识(不熟悉的朋友可以去Vue官网的API部分查看了解一下)。我们用Vue.extend从require.context获取的文件内容里面创建一个组件构造器,通过new构造器得到一个对话框的组件实例,再调用实例的$mount方法把它变成未挂载状态。最后我们通过document.body.appendChild方法把该未挂载的实例挂载到了body节点上,这样一来我们就完成了对话框文件的加载以及最后的对话框展示效果了。
理清楚了基本的操作之后,我们还需要取消对话框的回调方法,有时候我们还会在对话框结束之后向页面回调一些信息,为了完成这个共功能,这里在我们的对话框实例上绑定了onClose和onCallback方法。并且用Promise封装了弹框实例化的相关步骤,这都是为了顺利的向调用方传递回调结果,并可以做到流程的控制。需要注意的一点就是,当取消对话框的时候,我们把对话框用removeChild方法从body节点移除还需要手动调用对话框实例的$destroy()销毁实例,这样就完成最后的垃圾清理。
讲完了最核心的内容之后。我们来看最终实现的API处理流程:
第一步:我们的API会从require.context传入的文件地址里面去遍历获取以@开头,.vue结尾的文件上下文。
第二步:遍历这些上下文,获取一个dialogs映射对象。文件名除去@和.vue部分余下的字符串经过转化,最后以驼峰形式作为映射对象的key,而上下文的具体内容则成为了对应的value值。
第三步:编写createDialogAsync方法,从上下文内容中生成弹窗并进行最终的挂载展示。核心原理部分上文有讲到,这里不再重复。
第四步:编写init方法,把dialogs映射对象的所有上下文都绑上createDialogAsync,并返回一个新的dialogComponents
映射对象。dialogComponents的key和dialogs的key一样,只不过value换成了createDialogAsync函数。
第五步: 把init(dialogs)的结果绑定到Vue原型对象上,例如Vue.prototype.$dialog。这样我们就可以在任意组件内通过this.$dialog.XXXX()来调用弹框了。其中XXX是弹框文件的驼峰形式。
现在我们已经完成了懒加载对话框API的封装了。这里对弹框文件的文件名有命名的要求,必须是要以@开头,.vue结尾。如果你想自定义命名规范那么请同步修改require.context('./', true, /@([a-zA-Z\-0-9]+)\.vue$/, 'lazy')中的第三个参数,否则将会找不到弹框文件。下面我举例几个弹框文件的文件名和调用的方式:
1: 文件名:@confirm.vue
调用方式1: this.$dialog.Confrim()
调用方式2: await this.$dialog.Confirm() 或者 let ret = await this.$dialog.Confirm(); if (ret) xxxxxx
调用方式3: this.$dialog.Confrim().then((info) => {console.log(info)})
2: 文件名:@add-user.vue
调用方式1: this.$dialog.AddUser()
调用方式2: await this.$dialog.AddUser() 或者 let ret = await this.$dialog.AddUser(); if (ret) xxxxxx
调用方式3: this.$dialog.AddUser().then((info) => {console.log(info)})
到这里为止,懒加载对话框API的封装技巧的讲解就结束了。如有任何疑问,可与下方留言交流。
智能推荐
mybatis的懒加载
在Mybatis中有一种懒加载机制也叫做延迟加载,在前面的文章中,我们可以看到,对于pojo对象中内部的自定义对象要么专门写处理器处理要么利用association或者collection标签来进行关联。再利用标签是我列举了三种方法,其中有利用select属性来实现关联的,不过会产生N+1问题,但既然有整个属性必然有作用,而主要的作用便是可以使用懒加载。 懒加载是一种延迟技术,当我们没有使用被标记...
第一次面试凉经
1、int与Integer的区别 2、GET和POST两种基本请求方法的区别 3、Java线程的创建方式 4、在Java反射机制中获取Class对象有几种方法 5、SpringBoot读取properties配置文件中的数据有几种方式 Spring Boot最常用的3种读取properties配置文件中数据的方法: 1、使用@Value注解读取 读取properties配置文件时,默认读取的是ap...
Spring Quartz注入service为null,报错空指针
现象 jobDetail方法代码如下: 当在这个JobBean中通过@Resource或者@Autowired注入service相应的方法是,job执行,后台报错空指针。 解决方案一 添加一个类,代码如下: 然后在Job中通过该类调用获取对应的service,代码示例如下: 然后重启项目,问题解决。 解决方案二 重写JobFactory类 然后在spring配置文件内配置SchedulerFact...
【leetcode系列】【数据库】【简单】组合两个表
题目: 题目链接: https://leetcode-cn.com/problems/combine-two-tables/ 解题思路: 各种join的区别: inner join : 必须两个表都有数据,才会出结果 left outer join:左边的表有数据,就出结果 right outer join:右边的表有数据,就出结果 根据题意,不管Address表有没有数据...
yolov3训练讯飞安检图像数据集记录
yolov3训练讯飞安检图像数据集记录 前言 前置工作 数据集 yolov3配置 下载yolov3项目代码 修改Makefile文件并编译 实验 准备数据集 下载Imagenet上预先训练的权重 修改darknet/cfg/voc.data 修改darknet/data/voc.name 修改darknet/cfg/yolov3-voc.cfg 开始训练 测试识别 yolov3训练过程中输出参数详...
猜你喜欢
ubuntu1604编译安装apache2(httpd-2.4.18)+配置openssl环境
ubuntu1604编译安装apache2(httpd-2.4.18)+配置openssl环境 文章目录 ubuntu1604编译安装apache2(httpd-2.4.18)+配置openssl环境 下载安装包 执行编译安装脚本 修改配置文件 运行测试 当然您也可以通过浏览器访问以下网页看看是否ok 下载安装包 http://archive.apache.org/dist/apr/apr-uti...
高德地理编码与搜索服务-关键字查询的运用
1、注册高德开发平台 https://lbs.amap.com/ 2、创建应用并添加key 3、API文档 https://lbs.amap.com/api/webservice/guide/api/search 4、编写Python函数 5、PostgreSQL的Python函数 ...
698. Partition to K Equal Sum Subsets(M)
题目描述 给定一个数组和一个正整数k,能否把数组分为k个不为空的子集,使得这k个子集内所有元素的和相等,同时要求数组中元素大于0小于10000且 1 < k < 16,原题干如下, 首先确定几个变量,sum代表数组内所有元素的和,target代表每个子集内所有元素的和,做这道题时,我们得先得到target,不然无从下手。首先要抓住一个关键词,每个子集的元素和相等,这意味 target ...
spring boot neo4j简单学习
neo4j是一款高性能的NoSQL图形数据库,支持高效遍历,在机器学习、人工智能领域使用越来越广泛。 spring官网guides中有个例子:https://spring.io/guides/gs/accessing-data-neo4j/ 完成这个例子前需要手动下载neo4j server,我下载的是win-64bit。 下载地址:https://neo4j.com/download/other...
stl中vector内存分配与释放
1.vector的内存增长 vector特点: 内存空间只会增长,不会减小 元素以连续方式存放 对vector进行内存分配时,其实际分配的容量要比当前所需的空间多一些 为了支持快速的随机访问,vector容器的元素以连续方式存放,每一个元素都紧挨着前一个元素存储。设想一下,当vector添加一个元素时,为了满足连续存放这个特性,都需要重新分配空间、拷贝元素、撤销旧空间,这样性能难以接受。因此STL...
