前言
在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
现实问题
针对普通开发者,令人头痛的问题是
- vue2开发时,对于vue原生api的提示不是很全面开发效率降低
- 当项目越来越大的时候,模块化开发更加难以实现
- 针对于对象和数组的响应式,部分场景不适用
针对于更深层方面
- 虚拟dom更新时,很多未改变的地方也会重绘,造成dom的更新仍然涉及许多不必要的CPU工作
- vue2打包体积偏大,偏大的体积中,很多模块其实是未用到的
产生此问题的原因
- vue2采用的是
Facebook
的Flow
来进行语法提示。 - vue2中,针对方法是可以有效模块化的,但是针对要使用
Vue
中的API
的场景,只能使用mixin,但是mixin有致命的缺点(命名冲突和隐式依赖), 这往往是大型项目无法接受的。 - vue2采用的是
ES5
的Object.defineProperty()
来实现的,defineProperty
针对后加的属性是不会绑定响应式的,也就不会触发更新渲染。 - vue2提供类似于HTML的模板语法,但是,它是将模板编译成渲染函数来返回虚拟DOM树。Vue框架通过递归遍历两个虚拟DOM树,并比较每个节点上的每个属性,来确定实际DOM的哪些部分需要更新
- vue2中,大多数
全局API
是挂载在Vue
类中,在实际打包的过程中,无法实现按需打包,带来的影响是,有时候不必要的,未使用的代码文件都被打包了进去。
Vue3是如何解决的
针对问题一,支持typeScript
vue3采用typeScript
重写,在暴露出自有API
的同时,也暴露出来API
的接口,供我们开发时使用。在调用自有API
时,会进行语法提示和类型校验,大大提升了开发效率
针对问题二,采用hooks
Vue3借鉴了 react hook
实现了更加自由的编程方式,提出了Composition API
,允许开发者在js
文件中使用vue
的api
,这样的好处是,开发者可以针对复杂页面的每一个模块建立一个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
和内部帮助程序移动到Javascript
的module.exports
属性上实现这一点。这允许现代模式下的module bundler
能够静态地分析模块依赖关系,并删除与未使用的module.exports
属性相关的代码。模板编译器还生成了对树抖动友好的代码,只有在模板中实际使用某个特性时,该代码才导入该特性的帮助程序。 - 尽管增加了许多新特性,但Vue 3被压缩后的基线大小约为10 KB,不到Vue 2的一半。
Vue3总结
Vue3的优势
- 学习成本相对于其他框架低
- 在性能和开发上都大大提升了效率(dom更新,ts支持,响应式重写,
composition api
) - 家族生态系统的强大(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的优点
- 作为
JavaScript
的超集,相比于其他同类型语言(Dart
)不需要特定的执行环境,相对于前端开发者更容易掌握。 - 编译
JavaScript
是一门解释性语言。因此,需要跑起来才能测试他的正确性。这意味着你写了所有的代码即使有错误也不会提示错误。然后,针对错误,你可能要花很长时间来检查问题。TypeScript
编译器提供了错误检查的特性,如果发现某种语法错误,将在编译代码时提示出来,这有助于在脚本运行前就暴露出来错误
- 静态类型
JavaScript
是动态类型。无法提供可选的静态类型和类型推断系统。TypeScript
是静态类型语言。能够通过TLS(TypeScript Language Service)提供可选的静态类型和类型推断系统。TLS能够推断出无类型变量的类型
TypeScript
还支持已有的JS
库的类型定义。- 通过TS的描述文件(
.d.ts
结尾),能够为现有的JS
库提供描述,不需要将JS
改写成TS
- 通过TS的描述文件(
TypeScript
支持面向对象编程概念- 类、接口、继承、泛型...
TypeScript的缺点
- 需要一定的学习成本,有大量面向对象编程概念,接口(Interfaces)、泛型(Generics)、类(Classes)、枚举类型(Enums)等前端工程师可能不是很熟悉
- 会增加一些开发成本,开发前期可能要写很多的接口去定义数据,但是后期维护会非常方便,接口即注释
- 一些 JS库 需要兼容,提供声明文件,像vue2,底层支持的就不是很好(vue2.7除外)
- ts编译是需要时间的,这就意味着项目大了以后,开发环境启动和生产环境打包的速度就成了考验(这时,就需要Vite了)
浅谈Vite
背景
在早期开发过程中, 前端开发者们不仅要专心写代码, 还要
- 关注浏览器兼容性问题
- 手动维护依赖
- 模块化开发, 方便了开发者, 但是运行时, 增加了大量的js代码, 降低了页面访问效率
基于上面的问题, 构建工具 诞生, 这样以来, 开发者就可以专注于开发即可, 剩下的交由构建工具
什么是Vite
Vite(法语意思为‘快速的’), 是一种新型前端构建工具。你可以把它理解为一个开箱即用的开发服务器 + 打包工具的组合,但是更轻更快。Vite 利用浏览器原生的 ES 模块支持和用编译到原生的语言开发的工具(如 esbuild)来提供一个快速且现代的开发体验。
为什么要升级到vite
现实问题
在 web 迅速发展的时代, 前端担任着越来越重的任务。 当我们开始构建越来越大型的应用时,需要处理的 JavaScript 代码量也呈指数级增长。随之带来的就是
- 缓慢的服务器启动
- 缓慢的更新
造成开发的效率越来越低,项目不大,但是启动开发服务器却花了很长时间;更改一个字段,经过十几秒才热更新完成。
造成公司不得不配置更高性能的电脑给员工,从硬件上解决问题, 消耗更大的成本。
产生此问题的原因
其实这和webpack打包的原理有关系:
- 启动服务器慢,是因为在每次服务器启动之前。webpack会根据文件间的依赖关系对其进行静态分析,然后将这些模块按指定规则生成静态资源,当 webpack 处理程序时,它会递归地构建一个依赖关系图,找模块间的依赖,将各个模块进行合并,生成一个build,存入内存中,最后在启动服务器。所以速度上随着项目的增加,速度会越来越慢
- webpack的hmr。当你改动一个文件的时候,Webpack 的热更新会以当前修改的文件为入口重新 build 打包,所有涉及到的依赖也都会被重新加载一次。所以速度也随着项目的增加而降低
Vite 如何解决问题的
Vite 解决问题
- 针对 缓慢的服务器启动
通过EsBuild
预构建依赖,原生ESM(native ES modules)服务源码。实际上时让浏览器接管了打包程序的部分工作,当浏览器请求对应的URL时,Vite会拦截URL,提供对应的文件,进行转换并按需提供源码,根据情景动态导入代码, 并没有像webpack索引全部代码,所以启动事件始终为常量, 不会随着项目复杂度变高而延长,具体原理图如下:
- 针对 缓慢的热更新
当改动一个文件时,Vite 只需要精确地使已编辑的模块与其最近的 HMR 边界之间的链失活(大多数时候只是模块本身),失活的同时,利用 HTTP 头来加速整个页面的重新加载:- 源码模块(一般指并非直接是
JavaScript
的文件(JSX, CSS, Vue组件等等))的请求根据此请求是不是条件请求(304 Not Modified
)来进行协商缓存; - 依赖模块(一般指纯的
JavaScript
文件)请求则会通过Cache-Control: max-age=31536000,immutable
进行强缓存,因此一旦被缓存它们将不需要再次请求
- 源码模块(一般指并非直接是
Vite 总结
Vite的优势:
- 极速的服务启动,使用原生 ESM 文件,无需打包!
- 轻量快速的热重载, 无论应用程序大小如何,都始终极快的模块热重载(HMR)
- 丰富的功能,对 TypeScript、JSX、CSS 等支持开箱即用。
Vite的不足
- 在生产模式下还是需要打包,由于嵌套导入会导致额外的网络往返
- 相比于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. 更加模块化与可配置化,方便维护和阅读
- 本框架
-
项目全局功能可配置化
// 主题配置 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 },
-
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 }
-
插件引用模块可配置化
// plugin插件目录 └── vite │ └──plugin │ │ ├──compress // 代码压缩 │ │ ├──html // html配置 │ │ ├──imagemin // 图片资源压缩 │ │ ├──index // 入口文件 └──pwa // pwa
-
可使用hooks将代码中每一个功能拆分,拆分的hooks都可以单独使用vue的生命周期与API
二. 性能上
1. 打包体积
- 本框架(10.5M)
- vue2+js+webpack框架(17.4M)