Vue3+TypeScriptreview总结
时间:2023-03-16 14:20:22
科技观察
背景最近在开发一个物联网设备管理系统。其主要目的是通过物联网云平台对企业负责的智慧园区内的硬件设备进行综合管理和控制。由于该产品是一个实验项目,因此没有合同,也没有明确的利益。所以可用的资源很少。负责具体产品的只有1.5个人,几乎只有我一个人。所以你得做产品经理,做开发,做运维。但是,从技术角度来说,选择可以更加自由。整个系统在架构上被设计成4层。从下到上分别是设备硬件、设备接入网关、物联网平台、设备管理系统。除设备硬件外,其余三层均属于软件范畴。这篇文章主要记录了我在最后一层——设备管理系统前端开发的开发过程中的一些总结。前端使用Vite2.x、Vue3.x、Vuex4.x、VueRouter4.x、TypeScript、Element-Plus开发。可以看出这些框架和库使用的版本都比较激进,大部分都是最新版本,还有rc和beta版本。不过从立项到写这篇总结,有些库的版本已经不是最新的了,不得不感慨前端技术日新月异。思考一个组件首先看一个组件。WaterRipple.gif这是一个带有波纹效果的小圆点,用来表示当前的websocket连接状态。是一个非常简单的纯显示组件。样式效果是使用css3变量、动画以及前后伪类实现的。道具的设计很简单,只有一个类型字段。根据类型字段,波纹的颜色不同。思路有了,下面是实现的一些细节。如何使用名为枚举的字段声明类型?按照设计,类型字段应该是一个枚举值,不应由调用者任意设置。下面是Type的枚举声明,一共有6个字段。enumType{primary="primary",success="success",warning="warning",warn="warn",//warningaliasdanger="danger",info="info",}TypeScript中声明类型有两个关键字一是接口和类型,在声明具有不确定键类型的字段时略有不同。使用类型声明:typeColorConfig={[keyinType]:Colors;};useinterface但只能这样:interfaceColorConfig{[key:string]:Colors;}因为接口索引只能是基本类型,类型别名不能。类型的索引可以是复合类型。Vue3如何获取元素实例?在vue3中,组件的逻辑可以放在setup函数中,但是setup中没有this,所以vue2中的this.$refs的用法在vue3中无法使用。新的用法是:给元素添加ref属性。在设置中声明一个与元素引用同名的变量。在设置的返回对象中将ref变量作为同名属性返回。在onMounted生命周期中访问ref变量,即元素实例。第1步:
第2步:constpoint=ref
(null);注意HTMLDivElement中必须填写type,这样才能享受typeinference。第三步:返回{point};这一步是必不可少的,如果返回的对象中不包含这个同名属性,那么在onMounted中访问的ref对象将为null。第四步:onMounted(()=>{if(point?.value){//logic}});如何操作伪类?JavaScript获取不到伪类元素,但是你可以换个思路。伪类样式引用css变量,然后通过js控制css变量,完成间接操作伪类的效果。例如,这是一个伪类:.point-flicker:after{background-color:var(--afterBg);}这取决于afterBg变量。如果需要修改它的内容,只需要用js操作afterBg的内容即可。point.value.style.setProperty("--bg",colorConfig[props.type].bg);API变更Vue3中组件如何修改自己的props?有一种不太常见的情况是组件需要修改父组件传递给自己的props。比如drawer组件,mimicframe组件等。vue2中常见的用法有sync和v-model。vue3中只推荐v-model:xxx=""。例如父组件传递:子组件:... 在Vue3watch中更改watch使用变得更简单。import{watch}from"vue";watch(source,(currentValue,oldValue)=>{//logic});当source改变时自动执行watch第二个参数传入的函数。Vue3computed中对computed用法的更改也变得更简单。从“vue”导入{计算}constv=computed(()=>{returnx});computed返回的变量是一个响应对象。Vue3中组件自循环的技巧这是开发组件的技巧。假设您有一个不确定深度的树结构数据。{"label":"root","children":[{"label":"a","children":[{"label":"a1","children":[]},{"label":"a2","children":[]}]}]}它的类型定义如下:exportinterfaceMenu{id:string;label:string;children:Menu|null;}你需要实现一个树组件来渲染它们。这就是这项技术派上用场的地方。
{{menu.label}}
组件的名称可以直接在自身使用,无需在组件中声明。Vuex的一些陷阱:慎用Map在Vuex中,我设计了一个数据结构来存储模块的不同状态(业务概念)。typeCode=number;exporttypeModuleState=Map
;但是我发现了一个问题,当我修改Map中某个值中的某个属性时,不会触发Vuex的监听。所以只好将数据结构修改为对象的形式。exporttypeModuleState={[keyinCode]:StateProperty};ts中的索引不能使用类型别名,可以这样写:typeCode=number;exporttypeModuleState={[keyinCode]:StateProperty};此外,Map还有一个问题。当传入一个Map类型的Proxy对象作为参数时,不能使用get、set、clear等Map方法,但TypeScript会提示这些方法可用。如果使用这些方法,将引发UncaughtTypeError。如果使用Object,则不会出现此问题。WebSocket的异常是无法通过trycatch来监听的。ws的异常只能在onerror和onclose这两个事件中处理,trycatch无法捕获。有时,onerror和onclose会连续执行,比如触发onerror,导致连接关闭,onclose会立即触发。VueDevtoolsvuedevtools目前还不能支持Vue3,但是vuedevtools几乎是开发必备的工具。目前可以使用vuedevtoolsbeta版,但是存在一些bug。下载地址:https://chrome.google.com/webstore/detail/vuejs-devtools/ljjemllljcmogpfapbkkighbhhppjdbg?utm_source=chrome-ntp-icon使用方法很简单,安装后重启浏览器即可。不需要设置vue.config.devtools=true,vue3的vue.config实例中没有devtools属性。ESbuild安装依赖在使用vite启动服务时安装依赖,很容易出错。Error:EBUSY:resourcebusyorlocked,open'E:\gxt\property-relay-fed\node_modules\esbuild\esbuild.exe'是vite依赖的编译工具esbuild.exe被占用导致的。解决方法很简单,就是停止vite,安装好依赖后重启vite。Vite在Chrome中调试的问题系统中有一些移动页面,需要嵌入到App中。WebView的调试有两种常见的方式。一种简单的方法是使用腾讯开源的vcosnole,另一种比较麻烦的方法是使用Chrome的DevTools。但是vconsole并没有想象中的那么好用。image.png所以我选择使用chrome调试,chrome://inspect/#devices但是在调试的过程中,发现在chrome调试工具中运行的其实是ts源码,直接把ts的语法当成语法错误。(我用的是Vite启动的开发服务。)解决方案很简单,但是low。先用vitebuild将TS代码编译成JS,然后用vitepreview启动服务。WebSocketwebsocket和Vue3没有关系,这里简单提一下。设备管理系统的核心概念是设备。设备有很多属性,在硬件上也称为数据点。这些属性通过一个很长的链接指向用户界面。整体流程大致是:硬件通过tcp协议上传到接入网关,接入网关处理后通过mqtt协议上传到物联网平台。物联网平台通过规则引擎处理后,以webhookrestful的形式下发给业务系统。然后系统通过websocket推送到前端。虽然数据通过层层编解码和不同协议远距离呈现给用户,但前端只需要关心websockets即可。WebSocket重连在做重连的时候需要注意onerror和onclose不断执行的问题,一般使用类似防抖的方法解决。我的做法是添加一个变量来控制重连次数。让连接=假;//断开连接后,先触发onerror,再触发onclose,主要用于防止重复触发conn();functionconn(){connecting=false;if(ctx.state.stateWS.instance&&ctx.state.stateWS.instance.close){ctx.state.stateWS.instance.close();}consturl=ctx.state.stateWS.url+"?Authorization="+getAuthtication();ctx.state.stateWS.instance=newWebSocket(url);ctx.state.stateWS.instance.onopen=()=>{ctx.commit(ActionType.SUCCESS);};ctx.state.stateWS.instance.onclose=()=>{if(connecting)return;ctx.commit(ActionType.CLOSE);setTimeout(()=>{conn();},10*1000);connecting=true;};ctx.state.stateWS.instance.onerror=()=>{if(连接)return;ctx.commit(ActionType.ERROR);setTimeout(()=>{conn();},10*1000);connecting=true;};ctx.state.stateWS.instance.onmessage=function(this:WebSocket,ev:MessageEvent){//logic}catch(e){console.log("e:",e);}};}WebSocket连接活动日志系统设计为每天7*24小时运行.所以websocket很容易因为一些网络因素或者其他因素而断开连接。重连是一个很重要的功能,应该也有重连日志功能。在不同的用户环境下,查看WebSocket的连接状态非常麻烦。添加一个连接日志功能是一个很好的解决方案,这样你就可以看到不同时间的连接状态。image.png需要注意的是,这些日志保存在用户的浏览器内存中,需要设置一个上限。当达到上限时,早期的日志将被自动清除。WebSocket认证WebSocket认证是很多人容易忽视的一点。在我的系统设计中,restfulAPI的认证是通过在请求头中附加Authorization字段并设置生成的JWT来实现的。Websocket不能设置header,但是可以设置query,实现类似restful的认证设计。关于ws认证过期、续期、权限等问题,与restful保持一致即可。脚本设置:更干净的API脚本设置仍然是一个实验性功能,但它确实更干净。单文件组件setup的一般用法如下:setup,代码就变成了这样:sciprt标签中的顶层变量和函数会返回出来。这种模式减少了很多代码,可以提高开发效率,减轻脑力负担。但是此时也有几个问题,比如脚本设置中如何使用lifecycle和watch/computed函数?如何使用组件?如何获取道具和上下文?使用组件后直接导入组件,vue会自动识别,无需使用组件挂载。使用生命周期和监控计算函数和标准写法基本没有区别。使用了props和context,因为setup被提升到了script标签,自然没办法接收props的两个参数和上下文。所以vue提供了defineProps、defineEmit、useContext函数。definePropsdefineProps的用法和OptionsAPI中props的用法几乎一样。defineEmitdefineEmit的用法和OptionsAPI中emit的用法几乎一样。emit的第一个参数是事件的名称,它支持传递可变数量的参数。useContextuseContext是一个返回上下文对象的钩子函数。constctx=useContext()的原理很简单。添加了一层编译过程,将脚本设置编译成标准模式代码。不过在实现上还有很多细节,所以正式版还没有出。Vue3带来的模块化开发方式Composition这个技术栈带给我最深的感受还是开发方式的改变。在Vue2的开发中,OptionsAPI很难面对业务逻辑复杂的页面。当逻辑长达数千行时,跟踪变量的变化确实令人头疼。但是有了CompositionAPI,这就不再是问题了,它带来了一种新的开发方式,虽然有点React的感觉,但和以前相比已经很棒了!这个项目中的所有页面都是使用钩子开发的。在设备模块中我的js代码是这样的。每个模块各司其职,有自己的内部数据。如果各个模块需要共享数据,可以通过Vuex传递,或者在顶层组件的setup中,比如上面的reload函数。我的目录结构是这样的。image.png整体上很清爽,工程感越来越强。前端架构不同于后端架构。后端考虑更多的是高可用、高性能和可扩展性。前端考虑更多的是如何实现高内聚低耦合的分层设计,架构就是设计。一个好的架构设计可以大大提高开发效率,减轻开发者的精神负担。这是我们一直关心的问题。