任务59:从零开始封装一款DIALOG插件
1.导语:上节课带着大家封装了一款Z-tree树形菜单这样一个插件,而在封装这个插件的过程中,没有过多参数的配置,也没有过多功能的规划,只是把功能简单实现了,这个过程中,让大家初步了解插件封装的特点,插件封装基本上都是基于面向对象的。基于面向对象的好处在于创建每一个当前类的实例的话,都是一个独立的实例,实例的话就有自己的私有属性,还能调用一些共用的方法能实现我们私有和共用相结合的方式,这种方式对我们来说特别好,在过程中我们还可以把这个信息挂载到实例的私有属性上,这样的话在各个方法,只要this是实例,我就可以通过this来调用这些操作了。所以接下来,我们封装插件也好,封装类库也好,封装UI组件库也好。大家看到的优秀的源码基本上都是基于面向对象来实现的。包括后期Vue
和react也都是通过面向对象创建类及类的实例这种方式来完成的。这也是面向对象思想唯一在项目中对我们有用的,第一个是学会他思想,第二是能够插件组件封装。
2.目的: 今天给大家封装一个相对比较全一点复杂一点的插件,通过这个插件的封装让大家把插件封装这块再去掌握一下,把它里面的细节再去跑通了,跑好一点,这样以后面试的时候也好,在公司里也好,就可以放心大胆的造轮子了,而且以后再看别人插件、ul组件封装源码时候,也会很轻松,这样边去学习别人的思想,边完善自己的东西,未来你也可以写一个开源的插件。
3.功能简介今天封装的插件叫dialog,叫对话框,也可以叫模态框,这样的效果基本是借鉴jQuery中的dialog,jQuery中dialog刚开始有个框,在这个框里可以实现拖拽效果,点关闭的时候可以关闭,它还可以支持一些动画,这些动画以后也可以在自己的项目中支持起来,今天就不支持动画了。它还支持确认取消,它不仅有提示框,还有标题文字,支持确认和取消。还有自定义表单,还有message。除了jQuery中dialog,供我们参考,在我们Vue里面的Element UI组件库也给我们提供了好多组件,而这些组件里有message(消息提示),还有通知Notifination、MessageBox弹窗和dialog相似,但是它不支持拖拽。参考这两个里面常用的东西,我们也封装一个专业级的dialog插件,我们插件里把jQuery中dialog和Element融合在一起达到了一款自己的插件,调用方法一个叫messageplugin,另一个叫dialogplugin。先来看message,messageplugin里的option有一个message这个字段、type类型:infoinfo/warning/success/error,duration。 oninit(){},
onopen(){}, onclose(){}是提供的三个生命周期函数,刚开始把初始项都初始化好后就执行oninit(){},当初始化好,消息弹出来,相当于创建出消息,就执行 onopen(){}打开消息,当过了很长时间自动消失就执行 onclose(){},所以有三个钩子函数,在我们当前这个元素创建打开关闭分别执行这三个钩子函数。在dialog对话框里面,dialog执行,传一堆配置项,传一堆配置项,它会返回一个对应的实例,而实例里面提供open(),close()方法,让我们自己实现打开关闭,自己配置好后,默认是不打开的或打开的,也可以通过close()把它关闭,template可以指定一个模板,里面包含了表单元素,也可以仅仅指一个字符串,button默认是没有确认和取消的,如果你需要做确认按钮,你可以自己传一个对象,对象里面都是一个按钮和按钮的文字,如果你需要两个按钮,你就传两个按钮,点击按钮做什么事情都是自己来规划,同样,在它创建的各个阶段,触发它的3个钩子函数执行。
样式处理
今天封装的插件是有样式的,样式一般都有这几种,第一种是内置行内样式,以后把我插件的JS引进来,你不需要管样式,也不需要管结构,你只需要按照我提供的API文档调用就可以了,因为在插件内部已经把所有的样式都通过行内样式写好了,这样的缺点是代码里既有样式又有结构不方便维护,而且你写的样式都放在你动态创建的元素行内样式上,如果你以后想给创建的插件自己编写一些样式的话,你要改它的行内样式,这种方法不方便用户自己来配置一些样式,总而言之,也就是把你的样式通过行内样式集合到整个插件里。
- 内置行内样式
(1)不方便用户自定义样式
(2)代码管理起来不易
(3)好处:用户调用比较方便
this.$DIALOG = this.createDIV(`
display:'none';
position: fixed;
top: 0;
left: 0;
z-index: 9998;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, .8);
user-select: none;
opacity: 0;
transition: opacity .3s;
`);
createDIV(cssText = '', type = 'div') {
let div = document.createElement(type);
div.style.cssText = cssText;
return div;
}
- 外链样式表
①手动导入(推荐):用户可自己根据样式类,改为自己想要的样式 !important,同时没网也可以开发。
提供插件的js和插件的css,用插件的时候把两者手动导入即可,是目前插件最常用的方式,看似麻烦,实则很方便,第一个只要下载下来,哪怕没网也能用,boostrap和swiper都是这么干的。
②自动导入:自动导入需要保障网络和CSS的加载顺序
调用插件的时候首要任务:从cdn(分布式集群)上下载插件的样式,动态添加到页面中,缺点是并不是所有开发者的电脑都有网,如果没有网就不能通过CDN上把你样式拉下来了。
代码实现
这款插件提供messageplugin和dialogplugin两种方法,这两个方法执行的时候都能传一些对应的配置项,最后实现我们的效果,既然是一款插件实现出不同效果,提供出不同的方法,封装插件用创建类的方式来创建:首先创建一个闭包,防止它被全局变量污染。这里面创建一个类,这个类最终要实现效果的,这个类叫Dialog,这个类里面有constructor,就是我们要做的事情。并且最终提供给外面的方法一个叫messageplugin,执行这个方法的时候要不传一个字符串,字符串怎么处理一会再说,要不就传一个options,默认给一个空对象;还有一个方法叫dialogplugin,具体不管执行这两个种的哪个方法,最后都要通过dialog实现这个功能,插件的核心都是通过类来实现的,而我们都是通过创建这个类的实例,而messageplugin和dialogplugin的区别是传过来的options配置项不一样,不管怎么样这些配置项都是通过dialog来处理的,所以constructor里传一个options,不传默认是一个空对象,这两个方法默认都返回new Dialog(options),只不过要把传进来的options配置项都分别处理,当我们调用这些插件的时候可能会传很多配置项,每个方法里都有7个配置项,swiper里面的api有几百个,几百个或很多个参数该怎么处理,都让它传一个options ={}配置项,把这个配置项需要传哪个自己来指定,这样做的好处在于给函数传实参的时候不用按照顺序对齐也能按照我们想要传递的参数传过去,其他值不传也没关系,而传统定义形参的传参方式,比如下面代码就做不到这一点,不能跳,只能按照一定顺序依次传参,才能实现形参和实参的对应。所以说不管封装插件、类库还是普通的公共方法,如果传的参数比较多(两个以上),在两个以上的参数的时候为了有效管控我们的参数,为了让我们可以让有些东西传,或者不传;不传的话,赋值默认值,传的话以传的值为主,那么通过options ={}传参的方式是一个很好的方法。
function fn(a,b,c,d,e,f,g){
}
fn('a','c','e')
依次把status、message、type、duration等赋值默认值,三个钩子函数不传的话默认是匿名空函数,传的话以传的为主。同理把dialogplugin的参数依次传入。
(function(){
//核心
class Dialog{
constructor(options={
}){
}
}
//暴露给外面能调用的方法
const anonymous = Function.prototype;//=>Function的原型默认是匿名空函数 =>const anonymous = () =>{};
window.messageplugin = function messageplugin(options = {
}){
//init params
options=Object.assign({
//Object.assign就是合并两个对象,前边就是默认值,后边就是把你传进来的options替换一下
// 声明是message消息类型
status: 'message',
// 提示的文案内容
message: '',
// 展示的类型 info/warning/success/error
type: 'info',
// 自动消失的时间,设置为零则不会自动消失
duration: 3000,
// 钩子函数 创建/打开/关闭
oninit:anonymous,
onopen:anonymous,
onclose:anonymous
},options);
return new Dialog(options);
}
window.dialogplugin = function dialogplugin(options={
}){
options = Object.assign({
// 声明是dialog模态框类型
status: 'dialog',
// 内容模板 字符串/DOM元素对象
template: null,
// 提示的标题信息
title: '系统提示',
// 自定义按钮组信息
buttons: [],
// 钩子函数 创建/打开/关闭
oninit:anonymous,
onopen:anonymous,
onclose:anonymous
},options)
return new Dialog(options);
};
})();
以后不管调用messageplugin还是dialogplugin也好,我们一定是new Dialog执行它的constructor,我这些配置项一定会用在其他方法的某些参数,把配置项挂载到实例上,以后在其他方法里拿到options,然后解构出它的配置项就可以来用了。我们说了把所有实例挂载到原型上的好处是以后在其他实例种只要保证当前方法中的this,是当前的实例,那么就可以那就可以拿到这个值,实现多个方法间的参数共用性,这是我们用类创建插件、组件的好处。
【需求】:针对业务需求,这里面有一个配置项需要特殊处理下,在dialogplugin配置项中的template里面的文字内容,你可以传一个字符串,你也可以传一个模板字符串和DOM对象。
(1)直接把文字放内容区:temlate:“提示信息”
(2)模板字符串 template:<div>...</div>
(3)DOM对象,template:dialog
(4)某个配置项值是不同类型的,为了方便以后做的时候,把不同类型值转化成同样的类型值,比如说你传过来的是template,而template是一个字符串,那我就把字符串动态创建类似的DOM对象,最后你传给我对象我就用对象,你给我字符串我帮你创建成DOM对象,而以后处理的话,不管你传给我啥,反正最后都变成DOM对象了,直接插入到这个盒子里。这叫参数统一化,对于某些参数传不同类型值,你会发现我会把一种类型转成另一种类型,我就按一种类型来处理,这是一个小技巧,不是所有的东西都可以做,某些情况下可以做。
init()就是我们的大脑,以后我们做的事情都在init中管控,保证init()是当前的实例,接下来的两种插件创建出来的DOM 结构不一样,实现的功能效果也不一样,开发的时候可以分成两段开发,通过status的值不一样来干不同事情,初始化成功就是只要你创建完它的实例并且,他的参数配置项只要能挂载它的实例上,就代表初始化成功,初始化成功就执行init,然后开始我们的需求。
messageBox就是一个小提示,里面没有什么太复杂的结构,一个提示完了之后消失关闭,就没有了,我再想提示就创建一个新的盒子,每有一个提示我就创建一个新的div,消失关闭的时候就把这个div移除掉,就是这样频繁的创建div频繁移除。有些时候我们在项目中会遇到一种情况,我们情况比较复杂,比如dialog,如果发现页面中已经有dialog了,刚开始第一次已经创建dialog了,页面已经有提示了,当我点“X”的时候把它关闭掉,如果你也可以把它完全移除掉,以后再用再把它打开,再重新创建一个。但是dialog里面要提供一个方法,open()和close(),我能把它打开,我能把它关闭。换句话说我们页面中的dialog可以通过这两个方法打开和关闭,关闭的时候只是把它隐藏了,打开呢?再把它显示出来。close()的时候会让它消失吗?就是说不把它移除了,只是控制它隐藏而已。messageBox就创建一个新的盒子,每有一个提示我就创建一个新的div,消失关闭的时候就把这个div移除掉,dialog就是一旦你创建个dialogplugin,以后你再创建个dialogplugin就不需要重新创建了,只需要把里面的内容变成我们想要的,dialog代表的是另一种情况,就是一旦这个元素有了,以后只是控制显示和隐藏,不会再频繁的创建和移除了,换句话说我们页面种的dialog对话框有且只有一个,同时只能显示一个对话框,而且有且只有一个对话框,不想有多个对话框,你可以改对话框里面的内容,你可以在内容里干不同事情,做其他操作,对话框有且只有一个,以后用的时候执行dialogplugin,传一些参数,传一些参数就相当于打开了,通过open()打开,通过close()显示和隐藏,以后再一次执行dialog你可以不是重新创建,只是把内容改变,当然你也可以重新创建,当然你也可以重新创建,每执行一次dialogplugin就让它重新创建一次,但是关闭的时候绝对不是移除,这样关闭的时候还可以再打开,换句话说我像执行5个dialogplugin,我们页面中有5个dialogplugin,那我可以各自控制每一个dialog的open()与close()的方法,控制其显示和隐藏,这个需求看你自己来规划的。
浏览器的渲染机制
明明加了动画效果transition,但是为什么没有显示效果。原因是这样的
刚进来,我们通过create()方法创建元素document.body.appendChild(messageBOX);,刚把它放到页面中,紧接着执行handle(),紧接着执行open(),在open()里,立马让this._messageBox.style.top =20px;修改样式,一个要插入页面中进行渲染,另一个要改变样式也要渲染,当代浏览器有渲染队列机制,发现你两次相近的时间内,要改变它的样式,为了减少回流重绘,它只渲染一次。为了阻止这种浏览器默认渲染队列机制,只要在第二次改变样式之前获取任何的一个样式即可,本例中添加了this._messageBox.offsetTop;当我再次回到页面中进行测试便有了样式。
init() {
let {
status
} = this.options;
//message
if (status === "message") {
this.create();
this.handle();
this.open();
return;
}
//DIALOG
this.create();
this.handle();
this.open();
}
create() {
document.body.appendChild(messageBOX);
}
封装DIALOG插件实战

最低0.47元/天 解锁文章
6408

被折叠的 条评论
为什么被折叠?



