浅谈Vue3 + TS + Vite2 前端技术框架

浅谈Vue3 + TS + Vite2 前端技术框架

前言

  在web大环境的潮水中,时代一直在飞速进步,七猫前端现有框架(vue2)也迎来了升级,升级后的框架(vue3+ts+vite)带来了更高的开发效率与更好的浏览器性能。
  本文主要讲述一下自己在学习的过程中,对Vue3 + Ts + vite的理解, 并结合 一些概念已经开发的业务demo , 对此框架进行的详细介绍,以及展示升级后的demo带来的变化

tips:本文只涉及到vue3+ts+vite的概念、优势、起源和部分原理,不涉及代码与教程

介绍

浅谈Vue3

背景

  在过去的时间里,我们的网页变得更加动态化和强大了,多亏有了JavaScript,我们已经把传统的服务端代码放入了浏览器中,这样就产生了成千上万的Javascript代码,他链接了各式各样的HTML和CSS文件,但缺乏正规的组织形式,这也是为什么越来越多的开发者使用JavaScript框架的原因。

什么是Vue

Vue是一款友好的、多用途且高性能的JavaScript框架, 它可以帮助我们创建可维护性和可测试性更强的代码库。
Vue是渐进式的JavaScript框架,他可以嵌入到现有的服务端应用中,也可以作为新框架进行使用。

为什么要升级到Vue3

现实问题

针对普通开发者,令人头痛的问题是

  1. vue2开发时,对于vue原生api的提示不是很全面开发效率降低
  2. 当项目越来越大的时候,模块化开发更加难以实现
  3. 针对于对象和数组的响应式,部分场景不适用

针对于更深层方面

  1. 虚拟dom更新时,很多未改变的地方也会重绘,造成dom的更新仍然涉及许多不必要的CPU工作
  2. vue2打包体积偏大,偏大的体积中,很多模块其实是未用到的

产生此问题的原因

  1. vue2采用的是FacebookFlow来进行语法提示。
  2. vue2中,针对方法是可以有效模块化的,但是针对要使用Vue中的API的场景,只能使用mixin,但是mixin有致命的缺点(命名冲突和隐式依赖), 这往往是大型项目无法接受的。
  3. vue2采用的是ES5Object.defineProperty()来实现的,defineProperty针对后加的属性是不会绑定响应式的,也就不会触发更新渲染。
  4. vue2提供类似于HTML的模板语法,但是,它是将模板编译成渲染函数来返回虚拟DOM树。Vue框架通过递归遍历两个虚拟DOM树,并比较每个节点上的每个属性,来确定实际DOM的哪些部分需要更新
  5. vue2中,大多数全局API是挂载在Vue类中,在实际打包的过程中,无法实现按需打包,带来的影响是,有时候不必要的,未使用的代码文件都被打包了进去。

Vue3是如何解决的

针对问题一,支持typeScript

  vue3采用typeScript重写,在暴露出自有API的同时,也暴露出来API的接口,供我们开发时使用。在调用自有API时,会进行语法提示和类型校验,大大提升了开发效率

针对问题二,采用hooks

  Vue3借鉴了 react hook 实现了更加自由的编程方式,提出了Composition API,允许开发者在js文件中使用vueapi,这样的好处是,开发者可以针对复杂页面的每一个模块建立一个js文件,每个文件中定义响应式数据,使用watch,computed等方法,像编写函数一样自由地表达、组合和重用有状态的组件逻辑,最后在页面中直接引用,使代码更加的模块化

针对问题三,使用es6的Proxy

  Vue3使用了es6的Proxy代理来实现数据响应式,Proxy需要的是整体,不需要关心里面有什么属性,而且Proxy的配置项有13种,可以做更细致的事情,这是之前的defineProperty无法达到的
  带来的缺点就是,无法支持IE(但是vue将对IE支持的精力投入到vue2.7中,vue2.7兼容了vue3的新功能)

针对问题四,重写diff算法

引用尤大大的话:
为了实现这一点,编译器和运行时需要协同工作:编译器分析模板并生成带有优化提示的代码,而运行时尽可能获取提示并采用快速路径。这里有三个主要的优化:

  • 首先,在DOM树级别。我们注意到,在没有动态改变节点结构的模板指令(例如v-if和v-for)的情况下,节点结构保持完全静态。如果我们将一个模板分成由这些结构指令分隔的嵌套“块”,则每个块中的节点结构将再次完全静态。当我们更新块中的节点时,我们不再需要递归遍历DOM树 - 该块内的动态绑定可以在一个平面数组中跟踪。这种优化通过将需要执行的树遍历量减少一个数量级来规避虚拟DOM的大部分开销。
  • 其次,编译器积极地检测模板中的静态节点、子树甚至数据对象,并在生成的代码中将它们提升到渲染函数之外。这样可以避免在每次渲染时重新创建这些对象,从而大大提高内存使用率并减少垃圾回收的频率。
  • 第三,在元素级别。编译器还根据需要执行的更新类型,为每个具有动态绑定的元素生成一个优化标志。例如,具有动态类绑定和许多静态属性的元素将收到一个标志,提示只需要进行类检查。运行时将获取这些提示并采用专用的快速路径。

综合起来,这些技术大大改进了我们的渲染更新基准,Vue 3有时占用的CPU时间不到Vue 2的十分之一

针对问题五,取消全局挂载

引用尤大大的话:

  • 在Vue 3中,我们通过将大多数全局API和内部帮助程序移动到Javascriptmodule.exports属性上实现这一点。这允许现代模式下的module bundler能够静态地分析模块依赖关系,并删除与未使用的module.exports属性相关的代码。模板编译器还生成了对树抖动友好的代码,只有在模板中实际使用某个特性时,该代码才导入该特性的帮助程序。
  • 尽管增加了许多新特性,但Vue 3被压缩后的基线大小约为10 KB,不到Vue 2的一半。

Vue3总结

Vue3的优势

  1. 学习成本相对于其他框架低
  2. 在性能和开发上都大大提升了效率(dom更新,ts支持,响应式重写,composition api
  3. 家族生态系统的强大(Vuex,VueRouter,Pinia,Vue-CLI)

浅谈TypeScript

背景

JavaScript 曾是作为客户端语言引入的。NodeJS 的到来让 JavaScript 成为服务端语言的新星。然而,随着JS代码的增长,它变得更加混较难去维护和重用代码。除此之外,它没有采用面向对象,强类型检测以及编译时错误检查等特性,这些造成了js很难在企业级应用有所发展。
  因此,TypeScript诞生,用来弥补这些短板。

什么是TypeScript

Typed JavaScript at Any Scale
添加了类型系统的 JavaScript,适用于任何规模的项目。

TypeScript 是一种语言,它是JavaScript的超集,本质上是向JavaScript添加了可选的静态类型和基于类的面向对象编程:因此 JS 语法是合法的 TS。语法是指我们编写文本以形成程序的方式

为什么要升级到TypeScript

外部层面

数据

此图为 TypeScript 近几年的npm包下载量

支持

目前主流框架都已支持TypeScript,包括vue3、react、Angular,包括nodeJS之父重写DenoJS,也支持Ts。

内部层面

TypeScript 和 JavaScript 的区别
TypeScript JavaScript
JavaScript 的超集用于解决大型项目的代码复杂性 一种脚本语言,用于创建动态网页
可以在编译期间发现并纠正错误 作为一种解释型语言,只能在运行时发现错误
支持静态和动态类型, 支持类型推断 只支持动态类型
最终被编译成 JavaScript 代码,使浏览器可以理解 可以直接在浏览器中使用
支持模块、泛型和接口 不支持模块,泛型或接口
支持 ES3,ES4,ES5 和 ES6 等 不支持编译其他 ES3,ES4,ES5 或 ES6 功能, 但可通过babel实现
社区的支持仍在增长 大量的社区支持以及大量文档和解决问题的支持

TypeScript总结

TypeScript的优点

  1. 作为JavaScript的超集,相比于其他同类型语言(Dart)不需要特定的执行环境,相对于前端开发者更容易掌握。
  2. 编译
    • JavaScript是一门解释性语言。因此,需要跑起来才能测试他的正确性。这意味着你写了所有的代码即使有错误也不会提示错误。然后,针对错误,你可能要花很长时间来检查问题。
    • TypeScript编译器提供了错误检查的特性,如果发现某种语法错误,将在编译代码时提示出来,这有助于在脚本运行前就暴露出来错误
  3. 静态类型
    • JavaScript是动态类型。无法提供可选的静态类型和类型推断系统。
    • TypeScript是静态类型语言。能够通过TLS(TypeScript Language Service)提供可选的静态类型和类型推断系统。TLS能够推断出无类型变量的类型
  4. TypeScript还支持已有的JS库的类型定义。
    • 通过TS的描述文件(.d.ts结尾),能够为现有的JS库提供描述,不需要将JS改写成TS
  5. TypeScript支持面向对象编程概念
    • 类、接口、继承、泛型...

TypeScript的缺点

  1. 需要一定的学习成本,有大量面向对象编程概念,接口(Interfaces)、泛型(Generics)、类(Classes)、枚举类型(Enums)等前端工程师可能不是很熟悉
  2. 会增加一些开发成本,开发前期可能要写很多的接口去定义数据,但是后期维护会非常方便,接口即注释
  3. 一些 JS库 需要兼容,提供声明文件,像vue2,底层支持的就不是很好(vue2.7除外)
  4. ts编译是需要时间的,这就意味着项目大了以后,开发环境启动和生产环境打包的速度就成了考验(这时,就需要Vite了)

浅谈Vite

背景

  在早期开发过程中, 前端开发者们不仅要专心写代码, 还要

  • 关注浏览器兼容性问题
  • 手动维护依赖
  • 模块化开发, 方便了开发者, 但是运行时, 增加了大量的js代码, 降低了页面访问效率

  基于上面的问题, 构建工具 诞生, 这样以来, 开发者就可以专注于开发即可, 剩下的交由构建工具

什么是Vite

  Vite(法语意思为‘快速的’), 是一种新型前端构建工具。你可以把它理解为一个开箱即用的开发服务器 + 打包工具的组合,但是更轻更快。Vite 利用浏览器原生的 ES 模块支持和用编译到原生的语言开发的工具(如 esbuild)来提供一个快速且现代的开发体验。

为什么要升级到vite

现实问题

  在 web 迅速发展的时代, 前端担任着越来越重的任务。 当我们开始构建越来越大型的应用时,需要处理的 JavaScript 代码量也呈指数级增长。随之带来的就是

  • 缓慢的服务器启动
  • 缓慢的更新

造成开发的效率越来越低,项目不大,但是启动开发服务器却花了很长时间;更改一个字段,经过十几秒才热更新完成。
造成公司不得不配置更高性能的电脑给员工,从硬件上解决问题, 消耗更大的成本

产生此问题的原因

其实这和webpack打包的原理有关系:

  1. 启动服务器慢,是因为在每次服务器启动之前。webpack会根据文件间的依赖关系对其进行静态分析,然后将这些模块按指定规则生成静态资源,当 webpack 处理程序时,它会递归地构建一个依赖关系图,找模块间的依赖,将各个模块进行合并,生成一个build,存入内存中,最后在启动服务器。所以速度上随着项目的增加,速度会越来越慢
  2. webpack的hmr。当你改动一个文件的时候,Webpack 的热更新会以当前修改的文件为入口重新 build 打包,所有涉及到的依赖也都会被重新加载一次。所以速度也随着项目的增加而降低

Vite 如何解决问题的

Vite 解决问题

  1. 针对 缓慢的服务器启动
      通过 EsBuild预构建依赖,原生ESM(native ES modules)服务源码。实际上时让浏览器接管了打包程序的部分工作,当浏览器请求对应的URL时,Vite会拦截URL,提供对应的文件,进行转换并按需提供源码,根据情景动态导入代码, 并没有像webpack索引全部代码,所以启动事件始终为常量, 不会随着项目复杂度变高而延长,具体原理图如下:
  1. 针对 缓慢的热更新
      当改动一个文件时,Vite 只需要精确地使已编辑的模块与其最近的 HMR 边界之间的链失活(大多数时候只是模块本身),失活的同时,利用 HTTP 头来加速整个页面的重新加载:
    1. 源码模块(一般指并非直接是JavaScript的文件(JSX, CSS, Vue组件等等))的请求根据此请求是不是条件请求(304 Not Modified)来进行协商缓存;
    2. 依赖模块(一般指纯的JavaScript文件)请求则会通过 Cache-Control: max-age=31536000,immutable 进行强缓存,因此一旦被缓存它们将不需要再次请求

Vite 总结

Vite的优势:

  1. 极速的服务启动,使用原生 ESM 文件,无需打包!
  2. 轻量快速的热重载, 无论应用程序大小如何,都始终极快的模块热重载(HMR)
  3. 丰富的功能,对 TypeScript、JSX、CSS 等支持开箱即用。

Vite的不足

  1. 在生产模式下还是需要打包,由于嵌套导入会导致额外的网络往返
  2. 相比于webpack,生态还不够完善,目前支持的生态(vanilla, vue, react, preact, lit, svelte)

浅谈此框架(Vue3+Ts+Vite)

优势

  综上概述,这么多这么多的改变,总结起来就是

  • 即提升了开发者的开发效率
  • 又优化了项目在生产环境的性能。

其实这套框架所带来的改变不止上面提到的这些,还有更多更多。

缺陷

  由于此框架不支持IE,所以对于公司业务,只适用于后台管理系统,以及不需要支持IE的对外项目

在项目中的实践

以下为我在学习此框架时,所实践出来的结果。

对比上一代的框架优势

一. 开发效率上

1. 开发服务器启动时间

  • 本框架(0.9秒)
  • vue2+js+webpack框架(17秒)

2. 热更新

  • 本框架
    • 当检测到代码未更新时,不会进行热更新
    • 当检测到代码更新时,不会进行编译,但浏览器请求此页面时,借助浏览器的部分功能,实现热更新
  • vue2+js+webpack框架
    • 不论检测到代码是否更新,都会进行热更新,并且更新时间和更改大小内容有关

3. 错误检测

  • 本框架(编译时检测)
    有报错提示
  • vue2+js+webpack框架(未拥有)

4. 语法提示

  • 本框架(拥有语法提示)
  • vue2+js+webpack框架(未拥有)

5. 更加模块化与可配置化,方便维护和阅读

  • 本框架
  1. 项目全局功能可配置化

        // 主题配置
        theme: {
            // 是否展示主题切换按钮
            showDarkModeToggle: true,
            // 是否开启网站灰色模式,悼念的日期开启(4.4, 4.5, 12.13)
            grayMode: true
        },
        // 功能配置
        func: {
            // 是否展示菜单搜索按钮
            showSearchButton: true,
            // 是否开启回到顶部
            showBackTop: true,
            // 显示面包屑
            showBreadCrumb: true,
            // 左侧菜单栏是否可重复点击
            asideRepeatClick: false,
            // 切换界面的时候是否取消已经发送但是未响应的http请求。
            removeAllHttpPending: true,
            // 是否显示刷新按钮
            showReloadButton: true
        },
        cacheTabsSetting: {
            // 此按钮开启, 只是打开tab, 不会缓存页面
            show: true,
            // 是否开启KeepAlive缓存, 开启keepalive时, removeAllHttpPending失效
            openKeepAlive: true,
            // 是否展示快速操作
            showQuick: true,
            // 是否可以拖拽
            canDrag: true,
            // 刷新后是否保留已经打开的标签页
            cache: true
        },
        // 动画配置
        transition: {
            // 是否开启页面切换动画
            enable: true,
            // 是否打开页面切换loading
            openPageLoading: true,
            // 是否打开页面切换顶部进度条
            openNProgress: true
        },
    
  2. http通讯可配置化

        // 超时时间
        timeout: 10 * 1000,
        // 基础接口地址
        // baseURL: env.apiUrl,
        // 接口可能会有通用的地址部分,可以统一抽取出来
        // urlPrefix: import.meta.env.VITE_GLOB_API_URL,
        headers: { 'Content-Type': ContentTypeEnum.FORM_URLENCODED },
        // 数据处理方式
        transform,
        // 配置项,下面的选项都可以在独立的接口请求中覆盖
        requestOptions: {
            // 默认将prefix 添加到url
            joinPrefix: true,
            // 是否返回原生响应头 比如:需要获取响应头时使用该属性
            isReturnNativeResponse: false,
            //  是否加入时间戳
            joinTime: true,
            // 是否在请求中加入环境参数
            joinEnv: true,
            // 忽略重复请求
            cancelToken: true,
            // 是否携带token
            // withToken: true,
            // 消息提示类型
            errorMessageMode: 'message',
            // 接口地址
            apiUrl: env.apiUrl,
            // 接口拼接地址
            urlPrefix: env.urlPrefix
        }
    
  3. 插件引用模块可配置化

    // plugin插件目录
        └── vite
        │    └──plugin
        │    │   ├──compress // 代码压缩
        │    │   ├──html // html配置
        │    │   ├──imagemin // 图片资源压缩
        │    │   ├──index  // 入口文件
                 └──pwa // pwa
    
  4. 可使用hooks将代码中每一个功能拆分,拆分的hooks都可以单独使用vue的生命周期与API

二. 性能上

1. 打包体积

  • 本框架(10.5M)
  • vue2+js+webpack框架(17.4M)
展示评论