热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

开发笔记:Vue3+TypeScript复盘总结

篇首语:本文由编程笔记#小编为大家整理,主要介绍了Vue3+TypeScript复盘总结相关的知识,希望对你有一定的参考价值。

篇首语:本文由编程笔记#小编为大家整理,主要介绍了Vue3 + TypeScript 复盘总结相关的知识,希望对你有一定的参考价值。







点击下方“前端开发博客”关注公众号


回复“2”加入前端群


背景


近期在研发一套物联网设备管理系统,其主要用途是将公司旗下所负责智能园区中的硬件设备通过物联网云平台来进行综合管控。


由于这个产品是实验性项目,没有合同,没有明确收益。所以能够拿到的资源非常少。


产品具体的负责人,只有 1.5 人,几乎只有我自己。所以既要担任产品经理,又要担任开发者,还要担任运维。不过从技术角度而言,选型可以更加自由。


整个系统在架构上设计分为 4 层。自底向上分别是设备硬件、设备接入网关、物联网平台、设备管理系统。除去设备硬件,其它 3 层都属于软件范畴。


这篇文章主要记录一下我在开发最后一层-设备管理系统的前端开发过程中的一些总结。


前端采用 Vite2.x、Vue3.x、Vuex4.x、VueRouter4.x、TypeScript、Element-Plus 进行开发。可以看到,这些框架和库所采用的版本是比较激进的,大部分都是最新版本,以及 rc 和 beta 版本。不过从项目开始到写这篇总结,其中的一些库的版本已经不是最新的了,不得不感慨前端技术变化之快。


一个组件的思考


首先来看一个组件。




水波纹.gif

这是一个具有波纹效果、用来表示当前 websocket 连接状态的小圆点。是一个非常简单的纯展示组件。样式效果使用 css3 变量、动画、和 before、after 伪类实现。


props 设计非常简单,只有一个 type 字段。根据 type 字段的不同,波纹的颜色也不同。


思路有了,下面是实现上的一些细节性问题。


如何声明字段名为枚举的类型?


根据设计,type 字段应该是一个枚举值,不应该由调用方随意设置。


下面是 Type 的枚举声明,共有 6 个字段。


enum Type {
  primary = "primary",
  success = "success",
  warning = "warning",
  warn = "warn", // warning alias
  danger = "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 中无法使用。


新的用法是:


  1. 给元素添加 ref 属性。

  2. 在 setup 中声明与元素 ref 同名的变量。

  3. 在 setup 的 return 对象中将 ref 变量作为同名属性返回。

  4. 在 onMounted 生命周期中访问 ref 变量,既是元素实例。


第一步:




第二步:


const point &#61; ref<htmlDivElement | null>(null);

注意类型要填写 HTMLDivElement&#xff0c;这样才能享受类型推断。


第三步&#xff1a;


return { point };

这一步必不可少&#xff0c;如果返回对象中不包含这个同名属性&#xff0c;onMounted 中访问的 ref 对象会是 null。


第四步&#xff1a;


onMounted(() &#61;> {
  if (point?.value) {
    // logic
  }
});

如何操作伪类&#xff1f;


Javascript 无法获取到伪类元素&#xff0c;但是可以换一种思路。伪类样式引用 css 变量&#xff0c;再通过 js 控制 css 变量来完成间接操作伪类的效果。


比如这是一个伪类&#xff1a;


.point-flicker:after {
  background-color: var(--afterBg);
}

它依赖了 afterBg 变量。


如果需要修改它的内容&#xff0c;只需要使用 js 操作 afterBg 的内容即可。


point.value.style.setProperty("--bg", colorConfig[props.type].bg);

API 的变化


Vue3 中组件如何修改自身的 props&#xff1f;


有一种不是很常见的情况&#xff0c;需要组件修改父组件传递给自己的 Props。


比如抽屉组件、拟态框组件等。


在 vue2 中常见的用法是 sync 和 v-model。


vue3 中只推荐使用 v-model:xxx&#61;"" 的方式。


比如父组件传递&#xff1a;




子组件&#xff1a;





Vue3 中 watch 用法的变化


watch 变得更加简单。


import { watch } from "vue";
watch(source, (currentValue, oldValue) &#61;> {
    // logic
});

当 source 变化时自动执行 watch 第二个参数所传入的函数。


Vue3 中 computed 用法的变化


computed 也变得更加简单。


import { computed } from "vue"
const v &#61; computed(() &#61;> {
    return x
});

computed 返回的变量是一个响应式对象。


Vue3 中组件循环自身的技巧


这是一种开发组件的技巧。


假设你有一个不确定深度的树状结构数据。


{
  "label": "root",
  "children": [
    {
      "label": "a",
      "children": [
        {
          "label": "a1",
          "children": []
        },
        {
          "label": "a2",
          "children": []
        }
      ]
    }
  ]
}

它的类型定义如下&#xff1a;


export interface Menu {
  id: string;
  label: string;
  children: Menu | null;
}

你需要实现一种树状组件来渲染它们。这时就需要用到这种技巧。





组件的 name 可以在自身中直接使用&#xff0c;而不需要在 component 中声明。


一些坑


Vuex&#xff1a;慎用 Map


在 Vuex 中&#xff0c;我设计了一个数据结构用于存储模块&#xff08;业务概念&#xff09;不同的状态。


type Code &#61; number;
export type ModuleState &#61; Map;

但是我发现一个问题&#xff0c;当我修改 Map 中某一个 value 中的属性时&#xff0c;不会触发 Vuex 的监听。


所以我只好将数据结构修改为对象的形式。


export type ModuleState &#61; { [key in Code]: StateProperty };

ts 中索引不可以使用类型别名&#xff0c;但是可以写成下面这样&#xff1a;


type Code &#61; number;
export type ModuleState &#61; { [key in Code]: StateProperty };

除此之外&#xff0c;Map 还存在另外一个问题。


当一个 Map 类型的 Proxy 对象作为参数被传递时&#xff0c;是无法使用 get、set、clear 等 Map 方法的&#xff0c;但是 TypeScript 会提示这些方法可用。如果使用了这些方法&#xff0c;会得到一个 Uncaught TypeError。


如果使用 Object 则不会产生这个问题。


WebSocket 发生异常无法被 try catch 监听


ws 的异常只能在 onerror 和 onclose 两个事件中进行处理&#xff0c;try catch 是无法捕获的。


有些时候&#xff0c;onerror 和 onclose 会连续执行&#xff0c;比如触发 onerror&#xff0c;导致连接关闭&#xff0c;就会紧接着触发 onclose。


Vue Devtools


vue devtools 目前无法支持 Vue3&#xff0c;但是 vue devtools 几乎是开发中必不可少的工具&#xff0c;目前可以使用 vue devtools beta 版本&#xff0c;但存在一些 Bug。


下载地址&#xff1a;


https://chrome.google.com/webstore/detail/vuejs-devtools/ljjemllljcmogpfapbkkighbhhppjdbg?utm_source&#61;chrome-ntp-icon


用法非常简单&#xff0c;安装后重启浏览器就可以。不需要设置 vue.config.devtools &#61; true&#xff0c;在 vue3 中 vue.config 实例不存在 devtools 属性。


ESbuild 安装依赖


在使用 vite 启动服务的同时安装依赖&#xff0c;非常容易碰到一个错误。


Error: EBUSY: resource busy or locked, open &#39;E:\\gxt\\property-relay-fed\\node_modules\\esbuild\\esbuild.exe&#39;

这个问题的原因是 vite 依赖的编译工具 esbuild.exe 被占用所导致的&#xff0c;解决方法很简单&#xff0c;就是停掉 vite&#xff0c;安装完依赖后再重新启动 vite。


Vite 在 Chrome 中调试的问题


系统中有一些移动页面&#xff0c;需要嵌入在 App 中使用。


常见的调试 WebView 的方法有两种&#xff0c;一种简单的方式是使用腾讯开源的 vcosnole&#xff0c;另一种麻烦一些的调试方式是使用 Chrome 的 DevTools。


但是 vconsole 并没有想象中那么好用。




image.png

所以我选择使用 Chrome 调试&#xff0c;chrome://inspect/#devices


但是在调试过程中我发现 Chrome 调试工具里面竟然运行的是 TS 源码&#xff0c;TS 的语法直接被认为语法错误。&#xff08;我是使用 Vite 启动的开发服务。&#xff09;


解决方案很简单&#xff0c;但挺 Low。先使用 vite build 把 TS 代码编译成 JS&#xff0c;再使用 vite preview 启动服务。


WebSocket


websocket 和 Vue3 没什么关系&#xff0c;但是在这里简单提一下。


设备管理系统的核心概念是设备&#xff0c;设备会有很多属性&#xff0c;在硬件上也被称作数据点。这些属性会经历非常长的链路传输到用户界面上。整体流程大概是&#xff1a;硬件通过 tcp 协议上传到接入网关&#xff0c;接入网关处理后再通过 mqtt 协议上传到物联网平台&#xff0c;物联网平台再经过规则引擎处理&#xff0c;通过 webhook restful 的形式发送到业务系统&#xff0c;业务系统再通过 websocket 推送到前端。


虽然数据通过层层编解码、不同的协议绕了非常远的距离呈现到用户面前&#xff0c;但是前端只需要关心 websocket 就足够了。


WebSocket 重连


在做重连时&#xff0c;需要注意 onerror 和 onclose 连续执行的问题&#xff0c;通常是使用类似防抖的方法来解决。


我的做法是增加一个变量来控制重连次数。


let connecting &#61; false; // 断开连接后&#xff0c;先触发 onerror&#xff0c;再触发 onclose&#xff0c;主要用于防止重复触发
  conn();
  function conn() {
    connecting &#61; false;
    if (ctx.state.stateWS.instance && ctx.state.stateWS.instance.close) {
      ctx.state.stateWS.instance.close();
    }
    const url &#61; ctx.state.stateWS.url &#43; "?Authorization&#61;" &#43; getAuthtication();
    ctx.state.stateWS.instance &#61; new WebSocket(url);
    ctx.state.stateWS.instance.onopen &#61; () &#61;> {
      ctx.commit(ActionType.SUCCESS);
    };
    ctx.state.stateWS.instance.onclose &#61; () &#61;> {
      if (connecting) return;
      ctx.commit(ActionType.CLOSE);
      setTimeout(() &#61;> {
        conn();
      }, 10 * 1000);
      connecting &#61; true;
    };
    ctx.state.stateWS.instance.onerror &#61; () &#61;> {
      if (connecting) return;
      ctx.commit(ActionType.ERROR);
      setTimeout(() &#61;> {
        conn();
      }, 10 * 1000);
      connecting &#61; true;
    };
    ctx.state.stateWS.instance.onmessage &#61; function (
      this: WebSocket,
      ev: MessageEvent
    ) {
      // logic
      } catch (e) {
        console.log("e:", e);
      }
    };
  }

WebSocket 连接活动日志


系统是设计成 7*24 小时不间断运行。所以 websocket 很容易受到一些网络因素或者其它因素的影响发生断开&#xff0c;重连是一项非常重要的功能&#xff0c;同时还应该具备重连日志功能。


在用户的不同环境中&#xff0c;排查 WebSocket 的连接状态很麻烦&#xff0c;添加一个连接日志功能是比较不错的方案&#xff0c;这样可以很好的看到不同时间的连接情况。




image.png

需要注意&#xff0c;这些日志是存储在用户的浏览器内存中的&#xff0c;需要设置上限&#xff0c;到达上限要自动清除早期日志。


WebSocket 鉴权


websocket 的鉴权是很多人容易忽视的一个点。


我在系统设计中&#xff0c;restful API 的鉴权是通过在 request header 上附带 Authorization 字段&#xff0c;设置生成的 JWT 来实现的。


websocket 无法设置 header&#xff0c;但是可以设置 query&#xff0c;实现思路类似 restful 的认证设计。


关于 ws 鉴权的过期、续期、权限等问题&#xff0c;和 restful 保持一致即可。


script setup&#xff1a;更加清爽的 API


script setup 至今仍是一个实验性特性&#xff0c;但它确实非常清爽。


单文件组件的 setup 常规用法像下面这样&#xff1a;




使用 script setup 后&#xff0c;代码变成了下面这样&#xff1a;




在 sciprt 标签中的顶层变量、函数都会 return 出去。


在这种模式下&#xff0c;减少了大量代码&#xff0c;可以提高开发效率、降低心智负担。


但这时也存在几个问题&#xff0c;比如在 script setup 中怎么使用生命周期和 watch/computed 函数&#xff1f;怎么使用组件&#xff1f;怎么获取 props 和 context&#xff1f;


使用组件


直接导入组件后&#xff0c;vue 会自动识别&#xff0c;无需使用 component 挂载。




使用生命周期和监听计算函数


和标准写法基本无差异。




使用 props 和 context


由于 setup 被提升到 script 标签上了&#xff0c;自然也就没办法接收 props 和 context 这两个参数。


所以 vue 提供了 defineProps、defineEmit、useContext 函数。


defineProps


defineProps 的用法和 OptionsAPI 中的 props 用法几乎一致。




defineEmit


defineEmit 的用法和 OptionsAPI 中的 emit 用法也几乎一致。




emit 的第一个参数是事件名称&#xff0c;后面支持传递不定个数的参数。


useContext


useContext 是一个 hook 函数&#xff0c;返回 context 对象。


const ctx &#61; useContext()

原理


原理相当简单。增加了一层编译过程&#xff0c;将 script setup 编译成标准模式的代码。


但是实现上有非常多的细节&#xff0c;所以导致至今仍未推出正式版。


Vue3 Composition 所带来的模块化开发方式


这套技术栈带给我最深的感受还是开发方式上的变化。


在 Vue2 的开发中&#xff0c;Options API 在面对业务逻辑复杂的页面时非常吃力。当逻辑长达千行时&#xff0c;追踪一个变量的变化是一件非常头痛的事情。


但是有了 Composition API 后&#xff0c;这将不再是问题&#xff0c;它带来了一种全新的开发方式&#xff0c;虽然有种 React 的感觉&#xff0c;但这相比之前已经非常棒了&#xff01;


这项目中所有的页面&#xff0c;我都使用 hooks 的方式开发。


在设备模块中&#xff0c;我的 js 代码是这样的。




每个模块各司其职&#xff0c;各自有自己的内部数据&#xff0c;各个模块如果需要共享数据&#xff0c;可以通过 Vuex&#xff0c;或者在顶层组件的 setup 中传递&#xff0c;比如上面的 reload 函数。


我的目录结构是这样的。




image.png

整体上非常清爽&#xff0c;工程化的感觉越来越强。


前端架构不同于后端架构。


后端考虑的更多是高可用、高性能、可扩展。前端考虑的问题更多是如何实现高内聚低耦合的分层设计&#xff0c;架构即设计。


良好的架构设计能够极大的开发效率&#xff0c;降低开发人员的心智负担。


这也是我们一直以来所关注的问题。



作者&#xff1a;代码与野兽


https://juejin.cn/post/6950487211368251399



END



推荐阅读  点击标题可跳转


超全的Vue3文档【Vue2迁移Vue3】


Vue 项目性能优化技巧分享


Vue3有哪些不向下兼容的改变


关注下方「前端开发博客」&#xff0c;回复 “加群”


加入我们一起学习&#xff0c;天天进步



如果觉得这篇文章还不错&#xff0c;来个【分享、点赞、在看】三连吧&#xff0c;让更多的人也看到~



   “分享、点赞、在看” 支持一波  





推荐阅读
  • Vue基础一、什么是Vue1.1概念Vue(读音vjuː,类似于view)是一套用于构建用户界面的渐进式JavaScript框架,与其它大型框架不 ... [详细]
  • 单页面应用 VS 多页面应用的区别和适用场景
    本文主要介绍了单页面应用(SPA)和多页面应用(MPA)的区别和适用场景。单页面应用只有一个主页面,所有内容都包含在主页面中,页面切换快但需要做相关的调优;多页面应用有多个独立的页面,每个页面都要加载相关资源,页面切换慢但适用于对SEO要求较高的应用。文章还提到了两者在资源加载、过渡动画、路由模式和数据传递方面的差异。 ... [详细]
  • [译]技术公司十年经验的职场生涯回顾
    本文是一位在技术公司工作十年的职场人士对自己职业生涯的总结回顾。她的职业规划与众不同,令人深思又有趣。其中涉及到的内容有机器学习、创新创业以及引用了女性主义者在TED演讲中的部分讲义。文章表达了对职业生涯的愿望和希望,认为人类有能力不断改善自己。 ... [详细]
  • Voicewo在线语音识别转换jQuery插件的特点和示例
    本文介绍了一款名为Voicewo的在线语音识别转换jQuery插件,该插件具有快速、架构、风格、扩展和兼容等特点,适合在互联网应用中使用。同时还提供了一个快速示例供开发人员参考。 ... [详细]
  • 微信民众号商城/小顺序商城开源项目介绍及使用教程
    本文介绍了一个基于WeiPHP5.0开发的微信民众号商城/小顺序商城的开源项目,包括前端和后端的目录结构,以及所使用的技术栈。同时提供了项目的运行和打包方法,并分享了一些调试和开发经验。最后还附上了在线预览和GitHub商城源码的链接,以及加入前端交流QQ群的方式。 ... [详细]
  • 【前端工具】nodejs+npm+vue 安装(windows)
    预备先看看这几个是干嘛的,相互的关系是啥。nodejs是语言,类比到php。npm是个包管理,类比到composer。vue是个框架&# ... [详细]
  • 本文介绍了Python爬虫技术基础篇面向对象高级编程(中)中的多重继承概念。通过继承,子类可以扩展父类的功能。文章以动物类层次的设计为例,讨论了按照不同分类方式设计类层次的复杂性和多重继承的优势。最后给出了哺乳动物和鸟类的设计示例,以及能跑、能飞、宠物类和非宠物类的增加对类数量的影响。 ... [详细]
  • 深入理解Kafka服务端请求队列中请求的处理
    本文深入分析了Kafka服务端请求队列中请求的处理过程,详细介绍了请求的封装和放入请求队列的过程,以及处理请求的线程池的创建和容量设置。通过场景分析、图示说明和源码分析,帮助读者更好地理解Kafka服务端的工作原理。 ... [详细]
  • 企业数据应用挑战及元数据管理的重要性
    本文主要介绍了企业在日常经营管理过程中面临的数据应用挑战,包括数据找不到、数据读不懂、数据不可信等问题。针对这些挑战,通过元数据管理可以实现数据的可见、可懂、可用,帮助业务快速获取所需数据。文章提出了“灵魂”三问——元数据是什么、有什么用、又该怎么管,强调了元数据管理在企业数据治理中的基础和前提作用。 ... [详细]
  • 本文介绍了腾讯最近开源的BERT推理模型TurboTransformers,该模型在推理速度上比PyTorch快1~4倍。TurboTransformers采用了分层设计的思想,通过简化问题和加速开发,实现了快速推理能力。同时,文章还探讨了PyTorch在中间层延迟和深度神经网络中存在的问题,并提出了合并计算的解决方案。 ... [详细]
  • Oracle优化新常态的五大禁止及其性能隐患
    本文介绍了Oracle优化新常态中的五大禁止措施,包括禁止外键、禁止视图、禁止触发器、禁止存储过程和禁止JOB,并分析了这些禁止措施可能带来的性能隐患。文章还讨论了这些禁止措施在C/S架构和B/S架构中的不同应用情况,并提出了解决方案。 ... [详细]
  • 本文介绍了响应式页面的概念和实现方式,包括针对不同终端制作特定页面和制作一个页面适应不同终端的显示。分析了两种实现方式的优缺点,提出了选择方案的建议。同时,对于响应式页面的需求和背景进行了讨论,解释了为什么需要响应式页面。 ... [详细]
  • 如何实现JDK版本的切换功能,解决开发环境冲突问题
    本文介绍了在开发过程中遇到JDK版本冲突的情况,以及如何通过修改环境变量实现JDK版本的切换功能,解决开发环境冲突的问题。通过合理的切换环境,可以更好地进行项目开发。同时,提醒读者注意不仅限于1.7和1.8版本的转换,还要适应不同项目和个人开发习惯的需求。 ... [详细]
  • 本文介绍了一个Magento模块,其主要功能是实现前台用户利用表单给管理员发送邮件。通过阅读该模块的代码,可以了解到一些有关Magento的细节,例如如何获取系统标签id、如何使用Magento默认的提示信息以及如何使用smtp服务等。文章还提到了安装SMTP Pro插件的方法,并给出了前台页面的代码示例。 ... [详细]
  • HSRP热备份路由器协议的应用及配置
    本文介绍了HSRP热备份路由器协议的应用及配置方法,包括设计目标、工作原理、配置命令等。通过HSRP协议,可以实现在主动路由器故障时自动切换到备份路由器,保证网络连通性。此外,还介绍了R1和R2路由器的配置方法以及Sw1和Sw2交换机的配置方法,最后还介绍了测试连通性和路由追踪的方法。 ... [详细]
author-avatar
上海悠u7_
这个家伙很懒,什么也没留下!
Tags | 热门标签
RankList | 热门文章
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有