分发与封装网站源码分享?分发源码下载页面多模板

今天给各位分享分发与封装网站源码分享的知识,其中也会对分发源码下载页面多模板进行解释,如果能碰巧解决你现在面临的问题,别忘了关注本站,现在开始吧!

前言

前几天用Vue3重构了我那个Vue2的开源项目,最后还遗留了一个问题:项目中用的一个websocket插件还不能正常使用。于是,决定重写这个插,让其支持Vue3。

本文将记录下重写这个插件的过程并将其发布至npm仓库,顺便给插件作者提个PR

插件解读

image-20201103005333494

如上图所示就是即将要重构的插件,目前有735个star,我们先将插件代码clone到本地。

gitclonehttps://github.com/nathantsoi/vue-native-websocket\n

下载到本地后,用你喜欢的ide打开它,其目录如下:

image-20201101194150523

目录解读

经过一番梳理后,其各个目录的作用如下:

vue-native-websocket项目文件夹Emitter.jswebsocket的事件队列与分发的实现Main.jsvue插件入口代码Observer.js观察者模式,websocket服务核心功能封装build.js编译后的代码文件dist编译后的项目文件夹node_modules项目依赖库src项目源码文件夹test单元测试文件.eslintrc.json项目的eslint配置.gitignore上传至git仓库需要忽略的文件.nvmrc指定项目期望用的node版本.travis.yml自动化构建配置文件CHANGELOG.md版本发布记录文件npm-shrinkwrap.jsonnpm包版本锁定文件package.json项目依赖配置文件PUBLISH.md修改完插件后的发布规范README.md插件使用文档webpack.config.jswebpack配置文件yarn.lockyarn包版本锁定文件

读完代码后,我们发现他的实现逻辑很精简,一个字:妙。

该插件的核心代码就src目录下的3个文件,接下来我们就从插件的入口文件Main.js开始解读。

如下所示,它引入了两个文件以及Vue官方要求的插件作为一个对象时必须提供的install方法。

importObserverfrom&39;\nimportEmitterfrom&39;\n\nexportdefault{\ninstall(Vue,connection,opts={}){\n//…其它代码省略…//\n}\n}\n

那么,我们就先来看看第一个引入的文件Observer.js的代码。

如下所示,它引入了Emitter.js文件,以及它自身的实现代码。

importEmitterfrom&39;\n\nexportdefaultclass{\nconstructor(connectionUrl,opts={}){\n//…其它代码省略…//\n})\n}\n

Emitter.js

同样的,我们先从他引入的文件开始读,即Emitter.js,其代码如下,我读完代码后并添加了相关注释,它实现了一个事件监听队列,以及一个事件触发函数emit

classEmitter{\nconstructor(){\nthis.listeners=newMap()\n}\n\n/**\n*添加事件监听\n*@paramlabel事件名称\n*@paramcallback回调函数\n*@paramvmthis对象\n*@return{boolean}\n*/\naddListener(label,callback,vm){\nif(typeofcallback===&39;){\n//label不存在就添加\nthis.listeners.has(label)||this.listeners.set(label,[])\n//向label添加回调函数\nthis.listeners.get(label).push({callback:callback,vm:vm})\nreturntrue\n}\nreturnfalse\n}\n\n/**\n*移除监听\n*@paramlabel事件名称\n*@paramcallback回调函数\n*@paramvmthis对象\n*@return{boolean}\n*/\nremoveListener(label,callback,vm){\n//从监听列表中获取当前事件\nletlisteners=this.listeners.get(label)\nletindex\n\nif(listeners&&listeners.length){\n//寻找当前事件在事件监听列表的位置\nindex=listeners.reduce((i,listener,index)=>{\nif(typeoflistener.callback===&39;&&listener.callback===callback&&listener.vm===vm){\ni=index\n}\nreturni\n},-1)\n\nif(index>-1){\n//移除事件\nlisteners.splice(index,1)\nthis.listeners.set(label,listeners)\nreturntrue\n}\n}\nreturnfalse\n}\n/**\n*触发监听\n*@paramlabel事件名称\n*@paramargs参数\n*@return{boolean}\n*/\nemit(label,…args){\n//获取事件列表中存储的事件\nletlisteners=this.listeners.get(label)\n\nif(listeners&&listeners.length){\nlisteners.forEach((listener)=>{\n//扩展callback函数,让其拥有listener.vm中的方法\nlistener.callback.call(listener.vm,…args)\n})\nreturntrue\n}\nreturnfalse\n}\n}\n\nexportdefaultnewEmitter()\n\n

Observer.js

接下来,我们在回过头来看Observer.js的代码,他实现了websocket服务核心功能的封装,是这个插件的核心。它的constructor部分代码如下所示,他定义了插件调用者可以传的参数以及初始值。

constructor(connectionUrl,opts={}){\n//获取参数中的format并将其转成小写\nthis.format=opts.format&&opts.format.toLowerCase()\n\n//如果url以//开始对其进行处理添加正确的websocket协议前缀\nif(connectionUrl.startsWith(&39;)){\n//当前网站如果为https请求则添加wss前缀否则添加ws前缀\nconstscheme=window.location.protocol===&39;?&39;:&39;\nconnectionUrl=`${scheme}:${connectionUrl}`\n}\n\n//将处理好的url和opts赋值给当前类内部变量\nthis.connectionUrl=connectionUrl\nthis.opts=opts\n\n//是否开启重连,默认值为false\nthis.reconnection=this.opts.reconnection||false\n//最大重连次数,默认值为无穷大\nthis.reconnectionAttempts=this.opts.reconnectionAttempts||Infinity\n//重连间隔时间,默认为1s\nthis.reconnectionDelay=this.opts.reconnectionDelay||1000\n//重连超时id,默认为0\nthis.reconnectTimeoutId=0\n//已重连次数,默认为0\nthis.reconnectionCount=0\n\n//传输数据时的处理函数\nthis.passToStoreHandler=this.opts.passToStoreHandler||false\n\n//建立连接\nthis.connect(connectionUrl,opts)\n\n//如果配置参数中有传store就将store赋值\nif(opts.store){this.store=opts.store}\n//如果配置参数中有传vuex的同步处理函数就将mutations赋值\nif(opts.mutations){this.mutations=opts.mutations}\n//事件触发\nthis.onEvent()\n}\n

连接函数

我们再来看看connet方法的实现,它的代码如下,它会根据用户传入的websocket服务端地址以及插件参数来建立websocket连接。

//连接websocket\nconnect(connectionUrl,opts={}){\n//获取配置参数传入的协议\nletprotocol=opts.protocol||&39;\n//如果没传协议就建立一个正常的websocket连接否则就创建带协议的websocket连接\nthis.WebSocket=opts.WebSocket||(protocol===&39;?newWebSocket(connectionUrl):newWebSocket(connectionUrl,protocol))\n//启用json发送\nif(this.format===&39;){\n//如果websocket中没有senObj就添加这个方法对象\nif(!(&39;inthis.WebSocket)){\n//将发送的消息转为json字符串\nthis.WebSocket.sendObj=(obj)=>this.WebSocket.send(JSON.stringify(obj))\n}\n}\n\nreturnthis.WebSocket\n}\n\n

重连函数

我们再来看看reconnect方法的实现,它的代码如下,它会读取用户传进来的最大重连次数,然后重新与websocket服务端建立链接。

//重新连接\nreconnect(){\n//已重连次数小于等于设置的连接次数时执行重连\nif(this.reconnectionCount<=this.reconnectionAttempts){\nthis.reconnectionCount++\n//清理上一次重连时的定时器\nclearTimeout(this.reconnectTimeoutId)\n\n//开始重连\nthis.reconnectTimeoutId=setTimeout(()=>{\n//如果启用vuex就触发vuex中的重连方法\nif(this.store){this.passToStore(&39;,this.reconnectionCount)}\n\n//重新连接\nthis.connect(this.connectionUrl,this.opts)\n\n//触发WebSocket事件\nthis.onEvent()\n},this.reconnectionDelay)\n}else{\nif(this.store){\n//如果启用vuex则触发重连失败方法\nthis.passToStore(&39;,true)}\n}\n}\n

事件触发函数

我们再来看看onEvent函数,它的实现代码如下,它会调用Emitter中的emit方法,对websocket中的4个监听事件进行分发扩展,交由Emitter类来管理。

//事件分发\nonEvent(){\n[&39;,&39;,&39;,&39;].forEach((eventType)=>{\nthis.WebSocket[eventType]=(event)=>{\nEmitter.emit(eventType,event)\n\n//调用vuex中对应的方法\nif(this.store){this.passToStore(&39;+eventType,event)}\n\n//处于重新连接状态切事件为onopen时执行\nif(this.reconnection&&eventType===&39;){\n//设置实例\nthis.opts.$setInstance(event.currentTarget)\n//清空重连次数\nthis.reconnectionCount=0\n}\n\n//如果处于重连状态且事件为onclose时调用重连方法\nif(this.reconnection&&eventType===&39;){this.reconnect()}\n}\n})\n}\n

vuex事件处理函数

我们再来看看处理vuex事件的实现函数,它的实现代码如下,它用于触发vuex中的方法,它允许调用者传passToStoreHandler事件处理函数,用于触发前的事件处理。

/**\n*触发vuex中的方法\n*@parameventName事件名称\n*@paramevent事件\n*/\npassToStore(eventName,event){\n//如果参数中有传事件处理函数则执行自定义的事件处理函数,否则执行默认的处理函数\nif(this.passToStoreHandler){\nthis.passToStoreHandler(eventName,event,this.defaultPassToStore.bind(this))\n}else{\nthis.defaultPassToStore(eventName,event)\n}\n}\n\n/**\n*默认的事件处理函数\n*@parameventName事件名称\n*@paramevent事件\n*/\ndefaultPassToStore(eventName,event){\n//事件名称开头不是SOCKET_则终止函数\nif(!eventName.startsWith(&39;)){return}\nletmethod=&39;\n//事件名称字母转大写\nlettarget=eventName.toUpperCase()\n//消息内容\nletmsg=event\n//data存在且数据为json格式\nif(this.format===&39;&&event.data){\n//将data从json字符串转为json对象\nmsg=JSON.parse(event.data)\n//判断msg是同步还是异步\nif(msg.mutation){\ntarget=[msg.namespace||&39;,msg.mutation].filter((e)=>!!e).join(&39;)\n}elseif(msg.action){\nmethod=&39;\ntarget=[msg.namespace||&39;,msg.action].filter((e)=>!!e).join(&39;)\n}\n}\nif(this.mutations){\ntarget=this.mutations[target]||target\n}\n//触发store中的方法\nthis.store[method](target,msg)\n}\n

Main.js

上面我们读完了插件的核心实现代码,最后我们来看看插件的入口文件,它的代码如下,他会将我们前面实现的websocket相关封装应用到Vue全局。他做了以下事情:

全局挂载$socket属性,便于访问socket建立的socket连接启用手动连接时,向全局挂载手动连接方法和关闭连接方法全局混入,添加socket事件监听,组件销毁前移除全局添加的方法

importObserverfrom&39;\nimportEmitterfrom&39;\n\nexportdefault{\n\ninstall(Vue,connection,opts={}){\n//没有传入连接,抛出异常\nif(!connection){thrownewError(&39;)}\n\nletobserver=null\n\nopts.$setInstance=(wsInstance)=>{\n//全局属性添加$socket\nVue.prototype.$socket=wsInstance\n}\n\n//配置选项中启用手动连接\nif(opts.connectManually){\nVue.prototype.$connect=(connectionUrl=connection,connectionOpts=opts)=>{\n//调用者传入的参数中添加set实例\nconnectionOpts.$setInstance=opts.$setInstance\n//创建Observer建立websocket连接\nobserver=newObserver(connectionUrl,connectionOpts)\n//全局添加$socket\nVue.prototype.$socket=observer.WebSocket\n}\n\n//全局添加连接断开处理函数\nVue.prototype.$disconnect=()=>{\nif(observer&&observer.reconnection){\n//重新连接状态改为false\nobserver.reconnection=false\n}\n//如果全局属性socket存在则从全局属性移除\nif(Vue.prototype.$socket){\n//关闭连接\nVue.prototype.$socket.close()\ndeleteVue.prototype.$socket\n}\n}\n}else{\n//未启用手动连接\nobserver=newObserver(connection,opts)\n//全局添加$socket属性,连接至websocket服务器\nVue.prototype.$socket=observer.WebSocket\n}\nconsthasProxy=typeofProxy!==&39;&&typeofProxy===&39;&&/nativecode/.test(Proxy.toString())\n\nVue.mixin({\ncreated(){\nletvm=this\nletsockets=this.$options[&39;]\n\nif(hasProxy){\nthis.$options.sockets=newProxy({},{\nset(target,key,value){\n//添加监听\nEmitter.addListener(key,value,vm)\ntarget[key]=value\nreturntrue\n},\ndeleteProperty(target,key){\n//移除监听\nEmitter.removeListener(key,vm.$options.sockets[key],vm)\ndeletetarget.key\nreturntrue\n}\n})\nif(sockets){\nObject.keys(sockets).forEach((key)=>{\n//给$options中添加sockets中的key\nthis.$options.sockets[key]=sockets[key]\n})\n}\n}else{\n//将对象密封,不能再进行改变\nObject.seal(this.$options.sockets)\n\n//if!hasProxyneedaddListener\nif(sockets){\nObject.keys(sockets).forEach(key=>{\n//添加监听\nEmitter.addListener(key,sockets[key],vm)\n})\n}\n}\n},\nbeforeDestroy(){\nif(hasProxy){\nletsockets=this.$options[&39;]\n\nif(sockets){\nObject.keys(sockets).forEach((key)=>{\n//销毁前如果代理存在sockets存在则移除$options中给sockets添加过的key\ndeletethis.$options.sockets[key]\n})\n}\n}\n}\n})\n}\n}\n\n

插件重构

前面我们把插件整体地读了一遍,接下来就可以用Vue3+TypeScript来重构它了。

作者的代码写的很精巧,逻辑方面不用做改动,我只是将它的代码实现从js改成了ts,修改了被Vue3废弃的写法,虽然做的修改比较简单,但是学到了作者的插件设计思想以及踩到的一些ts的坑,收获还算挺大。

接下来,就跟大家分享下我的重构过程以及踩到的一些坑。

安装依赖

在用ts重构前,我们需要先安装相关依赖包,执行下述命令即可安装。

yarnaddtypescriptprettiereslinteslint-plugin-prettier@typescript-eslint/eslint-plugin@typescript-eslint/parserstandard–dev\n

随后,在项目根目录创建tsconfig.json文件,为typescript的配置文件,添加下述配置,设置&34;:true即可在运行tsc命令时自动在types目录下生成声明文件。

{\n&34;:[\n&34;\n],\n&34;:{\n&34;:[\n&34;,\n&34;\n],\n&34;:&34;,\n&34;:&34;,//打包到的目录\n&34;:&34;,//转换成的目标语言\n&34;:&34;,\n&34;:true,//是否生成声明文件\n&34;:&34;,//声明文件打包的位置\n&34;:true,//开启严格模式\n&34;:true,//便于浏览器调试\n&34;:&34;,//使用node模块\n&34;:true,//使用装饰器\n&34;:true,//跳过库检查\n&34;:true,//es模块互操作\n&34;:true,//允许默认导入\n&34;:true,//不能使用any\n&34;:true,//不能使用this\n&34;:true,//严格模式\n&34;:true,//不能有未使用的变量\n&34;:true,//不能有未使用的参数\n&34;:true//必须声明返回值\n},\n&34;:[\n&34;\n]//要打包的文件\n}\n\n

修改已经废弃的语法

在插件的入口文件Main.js中,插件需要向Vue全局挂载属性,即Vue.prototype.xx=xx,在vue3中这一写法已经废除,需要用app.config.globalProperties.xx=xx来替换,重构好的main.ts文件部分代码如下:

import{App}from&34;;\n\nexportdefault{\ninstall(app:App,connection:string,opts:websocketOpts={format:&34;}):void{\n//…其它代码省略….//\nopts.$setInstance=(wsInstance:EventTarget)=>{\n//全局属性添加$socket\napp.config.globalProperties.$socket=wsInstance;\n};\n}\n}\n

完整代码请移步:src/Main.ts

beforeDestroy生命周期被移除

在插件的入口文件app.mixin中,组件销毁前它需要从全局移除已经添加在全局的属性,即beforeDestroy,在Vue3中这一写法已经被移除,需要用beforeUnmount来替换,其部分代码如下:

import{App}from&34;;\n\nexportdefault{\ninstall(app:App,connection:string,opts:websocketOpts={format:&34;}):void{\n//….其它代码省略….//\napp.mixin({\nbeforeUnmount(){\nif(hasProxy){\nconstsockets=this.$options[&34;];\n\nif(sockets){\nObject.keys(sockets).forEach((key)=>{\n//销毁前如果代理存在sockets存在则移除$options中给sockets添加过的key\ndeletethis.$options.sockets[key];\n});\n}\n}\n}\n})\n}\n}\n

扩展全局对象

在Observer.ts中,需要向Websocket中添加sendObj方法,这在js中很简单,直接websocket.sendObj=()=>{}即可。但是在ts中它就会报错,Websocket中不存在sendObj方法,一开始我想在lib.dom.d.ts中定义这个方法,但是想了想这样做不妥,不能修改全局的库声明文件,毕竟这是插件。

image-20201102210949765

经过我的一番折腾后,在ts的文档中找到了答案,ts的官方文档描述如下。

image-20201102210650833

正如官方文档所描述,ts查找声明文件会从当前文件开始找,我们只需要在当前类中用declareglobal来扩展即可,代码如下:

//扩展全局对象\ndeclareglobal{\n//扩展websocket对象,添加sendObj方法\ninterfaceWebSocket{\nsendObj(obj:JSON):void;\n}\n}\n

添加上述代码后,报错就解决了,完整代码请移步:src/Observer.ts

image-20201102211101120

回调函数类型定义

在Emitter.ts文件里,添加监听的方法调用者可以传一个回调函数进去,这个回调函数的参数是未知的,因此就需要给他指定正确的类型,一开始我用的Function类型,但是eslint报错了,他不建议这么使用,报错如下:

image-20201102212611648

经过我的一番折腾后,找到了如下解决方案,声明类型时只需要将参数解构即可。

addListener(label:T,callback:(…params:T[])=>void,vm:T):boolean{\nif(typeofcallback===&34;){\n//label不存在就添加\nthis.listeners.has(label)||this.listeners.set(label,[]);\n//向label添加回调函数\nthis.listeners.get(label).push({callback:callback,vm:vm});\nreturntrue;\n}\nreturnfalse;\n}\n

完整代码请移步:src/Emitter.ts

验证插件能否正常工作

插件重构完成后,我们将整个项目的文件复制到一个vue3项目的node_modules/vue-native-websocket下,替换原先的文件。

image-20201103001444839

在main.ts中导入并使用插件。

import{createApp}from&34;;\n\nconstapp=createApp(App);\n//使用VueNativeSock插件,并进行相关配置\napp\n.use(store)\n.use(router)\n.mount(&app&34;userID&34;json&34;userID&34;userID&34;main&34;dist/Main.js&34;types&34;dist/types/Main.d.ts&34;scripts&34;build&34;tsc&34;commitlint&34;extends&34;@commitlint/config-conventional&34;husky&34;hooks&34;commit-msg&34;commitlint-EHUSKY_GIT_PARAMS&34;scripts&34;changelog&34;conventional-changelog-pangular-iCHANGELOG.md-s”\n},\n

生成的文件内容如下所示:

image-20201102235321074

插件发布

最后,我们就可以将插件发布至npm仓库了。

此外,重点内容在插件的重构,想从零开始学插件发布步骤的开发者可移步我的另一篇文章:Vue实现一个全屏加载插件并发布至npm仓库

在终端进入项目根目录,执行下述命令,登录npm仓库,输入自己的用户名和密码

npmlogin\n

image-20201103003251083

执行下属命令发布至npm仓库。

npmpublish–accesspublic\n

image-20201103003532065

插件发布成功,我们去npm仓库搜一下vue-native-websocket-vue3,如下所示,已经可以搜到了

npm仓库地址:vue-native-websocket-vue3

最后,我们就可以在项目中使用yarn来安装使用了。

原文:https://mp.weixin.qq.com/s/w_RPZ05XPlZsmG_I6SOhoA

分发与封装网站源码分享的介绍就聊到这里吧,感谢你花时间阅读本站内容,更多关于分发源码下载页面多模板、分发与封装网站源码分享的信息别忘了在本站进行查找哦。

Published by

风君子

独自遨游何稽首 揭天掀地慰生平