从实际项目出发,告诉你vue3到底香不香
点击上方 前端Q,关注公众号
回复加群,加入前端Q技术交流群
来源:代码与野兽
https://juejin.cn/post/6950487211368251399
背景
近期在研发一套物联网设备管理系统,其主要用途是将公司旗下所负责智能园区中的硬件设备通过物联网云平台来进行综合管控。
由于这个产品是实验性项目,没有合同,没有明确收益。所以能够拿到的资源非常少。
产品具体的负责人,只有 1.5 人,几乎只有我自己。所以既要担任产品经理,又要担任开发者,还要担任运维。不过从技术角度而言,选型可以更加自由。
整个系统在架构上设计分为 4 层。自底向上分别是设备硬件、设备接入网关、物联网平台、设备管理系统。除去设备硬件,其它 3 层都属于软件范畴。
这篇文章主要记录一下我在开发最后一层-设备管理系统的前端开发过程中的一些总结。
前端采用 Vite2.x、Vue3.x、Vuex4.x、VueRouter4.x、TypeScript、Element-Plus 进行开发。可以看到,这些框架和库所采用的版本是比较激进的,大部分都是最新版本,以及 rc 和 beta 版本。不过从项目开始到写这篇总结,其中的一些库的版本已经不是最新的了,不得不感慨前端技术变化之快。
一个组件的思考
首先来看一个组件。
这是一个具有波纹效果、用来表示当前 websocket 连接状态的小圆点。是一个非常简单的纯展示组件。样式效果使用 css3 变量、动画、和 before、after 伪类实现。
props 设计非常简单,只有一个 type 字段。根据 type 字段的不同,波纹的颜色也不同。
思路有了,下面是实现上的一些细节性问题。
如何声明字段名为枚举的类型?
根据设计,type 字段应该是一个枚举值,不应该由调用方随意设置。
下面是 Type 的枚举声明,共有 6 个字段。
enum Type {primary = "primary",success = "success",warning = "warning",warn = "warn", // warning aliasdanger = "danger",info = "info",
}
复制代码
TypeScript 中声明类型的关键字有两个,interface 和 type,在声明 key 不确定类型的字段时稍有不同。
使用 type 进行声明:
type ColorConfig = {[key in Type]: Colors;
};
复制代码
使用 interface 却只能像下面这样:
interface ColorConfig {[key: string]: Colors;
}
复制代码
因为 interface 的索引只能是基础类型,类型别名也不可以。而 type 的索引可以是复合类型。
Vue 3 如何获取元素实例?
在 vue3 中,组件的逻辑可以放在 setup 函数里面,但是 setup 中不再有 this,所以 vue2 中的 this.$refs 的用法在 vue3 中无法使用。
新的用法是:
给元素添加 ref 属性。
在 setup 中声明与元素 ref 同名的变量。
在 setup 的 return 对象中将 ref 变量作为同名属性返回。
在 onMounted 生命周期中访问 ref 变量,既是元素实例。
第一步:
<div class="point point-flicker" ref="point"></div>
复制代码
第二步:
const point = ref<HTMLDivElement | null>(null);
复制代码
注意类型要填写 HTMLDivElement,这样才能享受类型推断。
第三步:
return { 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。
比如抽屉组件、拟态框组件等。
在 vue2 中常见的用法是 sync 和 v-model。
vue3 中只推荐使用 v-model:xxx="" 的方式。
比如父组件传递:
<ws-log v-model="wsLogVisible" />
复制代码
子组件:
<template><div v-model:visible="visible">...</div>
</template><script>
// ...props: {visible: {type: Boolean,},},
</script>
复制代码
Vue3 中 watch 用法的变化
watch 变得更加简单。
import { watch } from "vue";watch(source, (currentValue, oldValue) => {// logic
});
复制代码
当 source 变化时自动执行 watch 第二个参数所传入的函数。
Vue3 中 computed 用法的变化
computed 也变得更加简单。
import { computed } from "vue"const v = computed(() => {return x
});
复制代码
computed 返回的变量是一个响应式对象。
Vue3 中组件循环自身的技巧
这是一种开发组件的技巧。
假设你有一个不确定深度的树状结构数据。
{"label": "root","children": [{"label": "a","children": [{"label": "a1","children": []},{"label": "a2","children": []}]}]
}
复制代码
它的类型定义如下:
export interface Menu {id: string;label: string;children: Menu | null;
}
复制代码
你需要实现一种树状组件来渲染它们。这时就需要用到这种技巧。
<template><div>{{ menu.label }}</div><Menu@select="select"v-for="item in menu.children":key="item.id":menu="item"/>
</template><script lang="ts">
import { defineComponent } from "vue";export default defineComponent({name: "Menu",props: {menu: {type: Object,},},
});
</script>
复制代码
组件的 name 可以在自身中直接使用,而不需要在 component 中声明。
一些坑
Vuex:慎用 Map
在 Vuex 中,我设计了一个数据结构用于存储模块(业务概念)不同的状态。
type Code = number;
export type ModuleState = Map<Code, StateProperty>;
复制代码
但是我发现一个问题,当我修改 Map 中某一个 value 中的属性时,不会触发 Vuex 的监听。
所以我只好将数据结构修改为对象的形式。
export type ModuleState = { [key in Code]: StateProperty };
复制代码
ts 中索引不可以使用类型别名,但是可以写成下面这样:
type Code = number;
export type ModuleState = { [key in Code]: StateProperty };
复制代码
除此之外,Map 还存在另外一个问题。
当一个 Map 类型的 Proxy 对象作为参数被传递时,是无法使用 get、set、clear 等 Map 方法的,但是 TypeScript 会提示这些方法可用。如果使用了这些方法,会得到一个 Uncaught TypeError。
如果使用 Object 则不会产生这个问题。
WebSocket 发生异常无法被 try catch 监听
ws 的异常只能在 onerror 和 onclose 两个事件中进行处理,try catch 是无法捕获的。
有些时候,onerror 和 onclose 会连续执行,比如触发 onerror,导致连接关闭,就会紧接着触发 onclose。
Vue Devtools
vue devtools 目前无法支持 Vue3,但是 vue devtools 几乎是开发中必不可少的工具,目前可以使用 vue devtools beta 版本,但存在一些 Bug。
下载地址
用法非常简单,安装后重启浏览器就可以。不需要设置 vue.config.devtools = true
,在 vue3 中 vue.config 实例不存在 devtools 属性。
ESbuild 安装依赖
在使用 vite 启动服务的同时安装依赖,非常容易碰到一个错误。
Error: EBUSY: resource busy or locked, open 'E:\gxt\property-relay-fed\node_modules\esbuild\esbuild.exe'
复制代码
这个问题的原因是 vite 依赖的编译工具 esbuild.exe 被占用所导致的,解决方法很简单,就是停掉 vite,安装完依赖后再重新启动 vite。
Vite 在 Chrome 中调试的问题
系统中有一些移动页面,需要嵌入在 App 中使用。
常见的调试 WebView 的方法有两种,一种简单的方式是使用腾讯开源的 vcosnole,另一种麻烦一些的调试方式是使用 Chrome 的 DevTools。
但是 vconsole 并没有想象中那么好用。
所以我选择使用 Chrome 调试,chrome://inspect/#devices
但是在调试过程中我发现 Chrome 调试工具里面竟然运行的是 TS 源码,TS 的语法直接被认为语法错误。(我是使用 Vite 启动的开发服务。)
解决方案很简单,但挺 Low。先使用 vite build 把 TS 代码编译成 JS,再使用 vite preview 启动服务。
WebSocket
websocket 和 Vue3 没什么关系,但是在这里简单提一下。
设备管理系统的核心概念是设备,设备会有很多属性,在硬件上也被称作数据点。这些属性会经历非常长的链路传输到用户界面上。整体流程大概是:硬件通过 tcp 协议上传到接入网关,接入网关处理后再通过 mqtt 协议上传到物联网平台,物联网平台再经过规则引擎处理,通过 webhook restful 的形式发送到业务系统,业务系统再通过 websocket 推送到前端。
虽然数据通过层层编解码、不同的协议绕了非常远的距离呈现到用户面前,但是前端只需要关心 websocket 就足够了。
WebSocket 重连
在做重连时,需要注意 onerror 和 onclose 连续执行的问题,通常是使用类似防抖的方法来解决。
我的做法是增加一个变量来控制重连次数。
let connecting = false; // 断开连接后,先触发 onerror,再触发 onclose,主要用于防止重复触发conn();function conn() {connecting = false;if (ctx.state.stateWS.instance && ctx.state.stateWS.instance.close) {ctx.state.stateWS.instance.close();}const url = ctx.state.stateWS.url + "?Authorization=" + getAuthtication();ctx.state.stateWS.instance = new WebSocket(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 (connecting) 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 的连接状态很麻烦,添加一个连接日志功能是比较不错的方案,这样可以很好的看到不同时间的连接情况。
需要注意,这些日志是存储在用户的浏览器内存中的,需要设置上限,到达上限要自动清除早期日志。
WebSocket 鉴权
websocket 的鉴权是很多人容易忽视的一个点。
我在系统设计中,restful API 的鉴权是通过在 request header 上附带 Authorization 字段,设置生成的 JWT 来实现的。
websocket 无法设置 header,但是可以设置 query,实现思路类似 restful 的认证设计。
关于 ws 鉴权的过期、续期、权限等问题,和 restful 保持一致即可。
script setup:更加清爽的 API
script setup 至今仍是一个实验性特性,但它确实非常清爽。
单文件组件的 setup 常规用法像下面这样:
<script lang="ts">
import { defineComponent } from 'vue'export default defineComponent({setup () {return {}}
})
</script>
复制代码
使用 script setup 后,代码变成了下面这样:
<script setup lang="ts"></script>
复制代码
在 sciprt 标签中的顶层变量、函数都会 return 出去。
在这种模式下,减少了大量代码,可以提高开发效率、降低心智负担。
但这时也存在几个问题,比如在 script setup 中怎么使用生命周期和 watch/computed 函数?怎么使用组件?怎么获取 props 和 context?
使用组件
直接导入组件后,vue 会自动识别,无需使用 component 挂载。
<script setup lang="ts">import C from "component"
</script>
复制代码
使用生命周期和监听计算函数
和标准写法基本无差异。
<script setup lang="ts">import { watch, computed, onMounted } from "vue"
</script>
复制代码
使用 props 和 context
由于 setup 被提升到 script 标签上了,自然也就没办法接收 props 和 context 这两个参数。
所以 vue 提供了 defineProps、defineEmit、useContext 函数。
defineProps
defineProps 的用法和 OptionsAPI 中的 props 用法几乎一致。
<script setup lang="ts">
import { defineProps } from "vue";interface Props {moduleID: string;
}const props = defineProps<Props>(["moduleID"]);
console.log(props.moduleID);
</script>
复制代码
defineEmit
defineEmit 的用法和 OptionsAPI 中的 emit 用法也几乎一致。
<script setup lang="ts">
import { defineEmit } from "vue";const emit = defineEmit(["select"]);
console.log(emit("select"));
</script>
复制代码
emit 的第一个参数是事件名称,后面支持传递不定个数的参数。
useContext
useContext 是一个 hook 函数,返回 context 对象。
const ctx = useContext()
复制代码
原理
原理相当简单。增加了一层编译过程,将 script setup 编译成标准模式的代码。
但是实现上有非常多的细节,所以导致至今仍未推出正式版。
Vue3 Composition 所带来的模块化开发方式
这套技术栈带给我最深的感受还是开发方式上的变化。
在 Vue2 的开发中,Options API 在面对业务逻辑复杂的页面时非常吃力。当逻辑长达千行时,追踪一个变量的变化是一件非常头痛的事情。
但是有了 Composition API 后,这将不再是问题,它带来了一种全新的开发方式,虽然有种 React 的感觉,但这相比之前已经非常棒了!
这项目中所有的页面,我都使用 hooks 的方式开发。
在设备模块中,我的 js 代码是这样的。
<script lang="ts">
import { defineComponent, toRefs } from "vue";
import { useDeviceCreate } from "./create";
import { useDeviceQuery } from "./query";
import { useDeviceDelete } from "./delete";
import { useUnbind } from "./unbind";
import { useBind } from "./bind";
import { useDeviceEdit } from "./edit";
import { useState } from "./state";
import { useAssign } from "./assign";export default defineComponent({setup() {const queryObj = useDeviceQuery();const { query, devices } = queryObj;const reload = query;return {...toRefs(useDeviceCreate(reload)),...toRefs(queryObj),...toRefs(useDeviceDelete(reload)),...toRefs(useUnbind(reload)),...toRefs(useBind(reload)),...toRefs(useDeviceEdit(reload)),...toRefs(useState(devices)),...toRefs(useAssign()),};},
});
</script>
复制代码
每个模块各司其职,各自有自己的内部数据,各个模块如果需要共享数据,可以通过 Vuex,或者在顶层组件的 setup 中传递,比如上面的 reload 函数。
我的目录结构是这样的。
整体上非常清爽,工程化的感觉越来越强。
前端架构不同于后端架构。
后端考虑的更多是高可用、高性能、可扩展。前端考虑的问题更多是如何实现高内聚低耦合的分层设计,架构即设计。
良好的架构设计能够极大的开发效率,降低开发人员的心智负担。
这也是我们一直以来所关注的问题。
内推社群
我组建了一个氛围特别好的腾讯内推社群,如果你对加入腾讯感兴趣的话(后续有计划也可以),我们可以一起进行面试相关的答疑、聊聊面试的故事、并且在你准备好的时候随时帮你内推。下方加 winty 好友回复「面试」即可。
从实际项目出发,告诉你vue3到底香不香相关推荐
- 浙大计算机学院多厉害,一张图,就能告诉你浙大到底有多牛!
原标题:一张图,就能告诉你浙大到底有多牛! 1977年恢复高考以后 各大学本科教育培养出的 院士校友人数排名 我们可以看到,在各大学本科教育培养出的院士校友人数排名上,浙大以23人的成绩遥遥领先. 而 ...
- 图解人工智能知识架构(从知识角度告诉你人工智能到底学些啥)
很多人都想学习人工智能,但是却不知道该学些啥?从宏观的视角搞清楚人工智能到底需要学习哪些领域的知识是至关重要的.这就好比要去逛一座大的商场,非常需要一份商场的楼层导览图,它能够告诉你各个楼层商户的分布 ...
- 图解人工智能知识架构(从系统角度告诉你人工智能到底学些啥)
很多人都想学习人工智能,但是却不知道该学些啥?从宏观的视角搞清楚人工智能到底需要学习哪些领域的知识是至关重要的.这就好比要去逛一座大的商场,非常需要一份商场的楼层导览图,它能够告诉你各个楼层商户的分布 ...
- 巴奴与海底捞的战争背后,“单品即品牌”战略到底香不香?
(图片来源于网络,侵删) 文 | 易不二 来源 | 螳螂财经(ID:TanglangFin) 在经受住了疫情的冲击后,餐饮业的生机已经逐步恢复了. 但海底捞似乎仍旧流年不利. 涨价.一片土豆一块五引发 ...
- 跟周报焦虑说拜拜!Excel打通FineBI,到底有多香
每月要做财务分析报表,以前我们是从各业务系统中导出Excel数据,粘贴到Excel周报.月报模板中,再通过邮件的方式发给领导,但苦恼的是重复导出太费时费力,而且一遇到大数据量,Excel就卡的不行. ...
- 王守义说13香,那iPhone12到底香不香?
点击上方"菜鸟学Python",选"星标"公众号 重磅干货,第一时间到达 iPhone12上线了,简直就是大型真香现场啊,而且卖的还非常火!说好的支持国产,坐等 ...
- 用卫星地图告诉你新疆到底有多大
1. 概述 没去过新疆,你都不知道新疆有多大,让我来换种方式,告诉你新疆多大,新疆面积166万平方公里,占全国的六分之一. 新疆位置与行政边界 比河南+山东+河北+北京+天津+山西+陕西+湖北+安徽+ ...
- Linux无处不在!让我来告诉你它到底在哪!
via: http://www.linuxfederation.com/linux-everywhere/ 译者:Mr小眼儿 本文由 LCTT 原创翻译,Linux中国 荣誉推出 "Linu ...
- 面试 AI 算法岗,项目实战与比赛经验到底能为你加成多少?
[翻到文末参与吴恩达<机器学习>课程,原价98元,现拼团价仅需9.9元,活动仅限2天!仅此一次!] 如果此刻你要去找一份AI算法岗的工作,但是你没有一个亮眼的项目经历,那么在AI大赛上取得 ...
- Vue项目实战——【基于 Vue3.x + Vant UI】实现一个多功能记账本(登录注册页面,验证码)
基于 Vue3.x + Vant UI 的多功能记账本(四) 文章目录 基于 Vue3.x + Vant UI 的多功能记账本(四) 项目演示 1.登录注册页面 2.图片验证码 3.修改 axios ...
最新文章
- WPF与缓动(一) N次缓动
- Python 爬虫系列:糗事百科最热段子
- 图像中某点绕点旋转后的坐标,图像旋转坐标位置
- java高级知识点_JAVA高级阶段知识点汇总
- 3389端口远程终端服务的全攻略
- 【原】docker部署单节点consul
- spring-jar包详解整理
- 【英语学习】【Level 08】U03 My Choice L3 Let's go to the movies
- 帆软报表插件开发之fine-decision中的AccessProvider扩展
- 华为防火墙USG6320透明模式配置
- js des加密 java_java端采用DES/CBC/PKCS5Padding 加密,js解密不了。google搜了好多方法没能解决...
- vs2015 vc14编译libcurl
- 北京54坐标系和1980西安坐标系转换
- 移动网络安装测试软件,家宽众测中国移动手机版(在线宽带网速测试器)V2.0.3 去广告版...
- python猜拳游戏教学_python实现猜拳游戏
- 基于MATLA的图像复原系统
- Apache HBase_GJF_MBY
- 大数据产品研究 - Presto简介
- “专业网络犯罪分子”对英国电信供应商进行 DDoS 攻击
- 【微服务架构】微服务设计模式
热门文章
- 谈谈移动硬盘或者手机连接到PC提示“无法访问,拒绝访问”或“您没有访问该设备的权限”的解决办法
- aspect ratio - 宽高比
- android自动适应横屏,Android屏幕适配(一)--自定义View屏幕适配
- 阿里云弹性云桌面、传统PC和虚拟桌面VDI区别对比
- Adobe Creative Cloud登陆中国大陆市场
- echarts中国及世界经纬度坐标
- 身体最佳排毒养生睡眠时间表
- php curl 模拟微信公众号登入
- linux权限不够【操作方案】
- 百度网盘加速下载方法——系统自带的提速模式和积分模式区别