- 前端工程质量保障体系实践
- 曾静益
- 5字
- 2024-02-27 16:14:03
02 工程搭建
2.1 类型约束——TypeScript
2.1.1 介绍
由于JavaScript语言本身的局限性,它注定难以胜任大型项目的开发和维护。因此,微软开发了TypeScript来解决这个问题。TypeScript是在JavaScript的基础上添加静态类型定义构建而成的,它能够被tsc编译器或Babel转译为JavaScript代码,运行在任何浏览器中。
TypeScript发展至今,已经成为大型项目的标配,它提供的静态类型系统大大增强了代码的可读性及可维护性。TypeScript设计了一套类型机制来保证编译时的强类型判断:变量在初始化时就会被明确指定类型,后续任何其他类型的赋值都将引起编译错误。在下面的示例中,初始化时定义foo为数字1,TypeScript将自动进行类型推断,明确foo的类型是number。当开发人员尝试将字符串'2'赋值给foo时,就会自动进行错误提示。
![](https://epubservercos.yuewen.com/211D7B/26763692509344406/epubprivate/OEBPS/Images/43579_34_1.jpg?sign=1734435636-dV7q98IIKxvx9BmNQxz1quJtmZHPIIyi-0-c20c9bea17acab59179ca53c15c4ebbf)
同时,TypeScript提供了最新的JavaScript特性,可以帮助开发人员使用更前沿的API能力构建更友好、高性能的组件。通过使用TypeScript,能有效提升前端工程代码的可读性和可维护性。
• TypeScript增强了开发工具的能力,能够根据编码情况自动进行代码补全、文件引用跳转等操作。
• 类型定义弥补了前端文档缺失的问题,通过类型定义即可知道对应字段的含义和函数的使用方式。
• 强类型检测使代码迁移、重构以及手误等原因导致的变量名写错,可以在编译阶段提前暴露。
TypeScript对于JavaScript的兼容性极强,开发人员可以对项目做渐进式迁移。即使在迁移过程中遇到TypeScript编译报错,开发人员也可以暂时跳过报错,快速生成JavaScript文件。
开源社区许多优秀的第三方库都是基于JavaScript实现的,并不支持TypeScript的类型定义。针对这一问题,TypeScript也进行了特殊处理。第三方库可以选择使用TypeScript进行重写来支持类型提示,也可以选择编写单独的类型文件(.d.ts)来实现类型定义,只需要保证类型文件名和原文件名相同且处于同一路径下即可。开源社区大部分的主流第三方库都已经支持TypeScript的类型定义,例如,著名的jQuery。
![](https://epubservercos.yuewen.com/211D7B/26763692509344406/epubprivate/OEBPS/Images/43579_35_1.jpg?sign=1734435636-yDvkcekrmnlSWgkW7DyXidgRyuF37C9E-0-831185546587e65ae60dc3c31e7208dc)
TypeScript作为JavaScript的超集是非常成功的,无论是类型检测、语法提示等功能,还是丰富的开源社区生态,都为其取得巨大成功做出了贡献。
2.1.2 基础知识
TypeScript是JavaScript的超集,因此JavaScript的基础类型也适用于TypeScript。
布尔(boolean):最基本的数据类型,仅有两个简单的值,分别是true和false。
![](https://epubservercos.yuewen.com/211D7B/26763692509344406/epubprivate/OEBPS/Images/43579_35_2.jpg?sign=1734435636-rdldYW7nhbVlrVCYdls5uF1gxhMaIR0C-0-1273e8eef189c926c01d129593b35cab)
数字(number):和JavaScript一样,TypeScript里的所有数字都是浮点数。除了支持十进制和十六进制字面量,TypeScript还支持ECMAScript 2015中引入的二进制和八进制字面量。
![](https://epubservercos.yuewen.com/211D7B/26763692509344406/epubprivate/OEBPS/Images/43579_35_3.jpg?sign=1734435636-Eo2fAawvCWCLMDVuH3Yek0PFDqX5Ztzy-0-9d6a319fcb19543f3be8fcc445b4a752)
字符串(string):使用string表示文本数据类型。TypeScript和JavaScript一样,可以使用双引号(")或单引号(')表示字符串。
![](https://epubservercos.yuewen.com/211D7B/26763692509344406/epubprivate/OEBPS/Images/43579_35_4.jpg?sign=1734435636-2GzL5hKjJO8IRDqR07WHClIozd2yYEdQ-0-12b5f8aabacdd8d56cf7397cf32eb32c)
数组(Array):有两种方式可以定义数组。
![](https://epubservercos.yuewen.com/211D7B/26763692509344406/epubprivate/OEBPS/Images/43579_35_5.jpg?sign=1734435636-M8YYQx5eBhU75ka9LZOBYrriRmuDWyPB-0-baabd0e6471fb17bbbcba40d0d3a9380)
![](https://epubservercos.yuewen.com/211D7B/26763692509344406/epubprivate/OEBPS/Images/43579_36_1.jpg?sign=1734435636-OLsYV1qeoyLsBQpaLlWAK948eCkNgbuJ-0-1b30ceeb8aa6ec745bc924debdf047fa)
对象(object):可以直接使用object进行类型声明,也可以分字段进行具体声明。
![](https://epubservercos.yuewen.com/211D7B/26763692509344406/epubprivate/OEBPS/Images/43579_36_2.jpg?sign=1734435636-iQwtV4NeHFISfdWZ2tnpB9RCNlQHsVsn-0-04b3abe7a174d9f25c00dff0b5ef6ccc)
空(null):对应的类型也是null。
![](https://epubservercos.yuewen.com/211D7B/26763692509344406/epubprivate/OEBPS/Images/43579_36_3.jpg?sign=1734435636-WlicaCDh14S0hpyL1UHsEjQDTTQhUVzm-0-f9dba1b4c45dc91bcd91aa4046049a2c)
未定义(undefined):对应的类型也是undefined。
![](https://epubservercos.yuewen.com/211D7B/26763692509344406/epubprivate/OEBPS/Images/43579_36_4.jpg?sign=1734435636-PvULYAGI6qucrvgce8QSxESYzglLGfKL-0-52de469b047733df6e0bef8d17efb284)
标志(symbol):ES6引入的一种新的原始数据类型,表示独一无二的值。
![](https://epubservercos.yuewen.com/211D7B/26763692509344406/epubprivate/OEBPS/Images/43579_36_5.jpg?sign=1734435636-bBsLMHJbfWQRPe4X6rrwIZPnhplsMEs2-0-61d1d71ff918d659ccd08829faf66231)
除此之外,TypeScript为了完善类型系统,还定义了以下基础类型。
never类型表示永不存在的值的类型。它可以是任何类型的子类型,也可以赋值给任何类型。但是,没有任何类型是never的子类型或可以赋值给never类型(除never本身外),即使any也不可以赋值给never。例如,never类型是那些总是抛出异常或根本不会有返回值的函数表达式或箭头函数表达式的返回值类型。
![](https://epubservercos.yuewen.com/211D7B/26763692509344406/epubprivate/OEBPS/Images/43579_36_6.jpg?sign=1734435636-soRbrZ94qe9n1dqVW8ON5o7ey7dzAIWG-0-3da04a8a33918d5890d9e6e492467ae5)
![](https://epubservercos.yuewen.com/211D7B/26763692509344406/epubprivate/OEBPS/Images/43579_37_1.jpg?sign=1734435636-ZqoaARaQGo2YmC64ZLJwMLpC2ztX984S-0-3d111d51eda4ace0b782bff43115a472)
any类型表示当前值可能为任意类型。当前值可能来自动态的内容,比如用户输入或第三方代码库。在这种情况下,开发人员希望类型检查器不对这些值进行检查,直接让它们通过编译阶段的检查。
![](https://epubservercos.yuewen.com/211D7B/26763692509344406/epubprivate/OEBPS/Images/43579_37_2.jpg?sign=1734435636-WhW6HV6tYZEFCJrXnK2kvbaIFRArh3Qs-0-367a43efb6560b95729bb9b64be463e1)
void类型表示没有任何类型,null和undefined被赋值给类型为void的变量。当一个函数没有返回值时,其返回值类型会被自动推断为void。
![](https://epubservercos.yuewen.com/211D7B/26763692509344406/epubprivate/OEBPS/Images/43579_37_3.jpg?sign=1734435636-g85rPGZAAlIR7TJJNlDYtOqhVOQo76RI-0-081b01f5dce7ff6c506c2574fcccbd90)
枚举(enum)是TypeScript对JavaScript基础数据类型的一个补充。枚举用于声明一组命名的常数。当一个变量有几种可能的取值时,可以将它定义为枚举类型,从而有效地防止开发人员提供无效值。
![](https://epubservercos.yuewen.com/211D7B/26763692509344406/epubprivate/OEBPS/Images/43579_37_4.jpg?sign=1734435636-aPTPzqcR34sGAVJwWD6vo7xLdncrMdqO-0-dcdf561f31c9c89da4c1ee934f904ea1)
元组(tuple)属于数组类型的一个变种,用来表示一个已知元素的数量和类型,各元素的类型可以不同。
![](https://epubservercos.yuewen.com/211D7B/26763692509344406/epubprivate/OEBPS/Images/43579_37_5.jpg?sign=1734435636-guvn1B2f8ZvRfzc8bfupKUNOv6sA9G24-0-a6cbba4b0ec7b510a947fa15b6e4fea0)
泛型可以支持未来的数据类型以及做一些数据推断。泛型在实际中应用极其广泛,例如,泛型函数、泛型类型等。
![](https://epubservercos.yuewen.com/211D7B/26763692509344406/epubprivate/OEBPS/Images/43579_37_6.jpg?sign=1734435636-58YqCvdBBLl8EGPtcno03XI9zUOi4GZm-0-8307598de24a73910e29a423702f5911)
![](https://epubservercos.yuewen.com/211D7B/26763692509344406/epubprivate/OEBPS/Images/43579_38_1.jpg?sign=1734435636-4Ka24YIxvUmcNJNa22BjoLNmGw5RHdQO-0-dda5c10b35a10355fcc06e55724e3cc7)
继承(extends)可以使得子类具有父类的属性和方法,或对父类进行重新定义,追加属性和方法。
![](https://epubservercos.yuewen.com/211D7B/26763692509344406/epubprivate/OEBPS/Images/43579_38_2.jpg?sign=1734435636-sLut4CqGIbMop2zA5UU7Ti7FXtnSQhAH-0-944e347e00eb8e5dbcf94553e79670b1)
2.1.3 高级类型
TypeScript除了拥有前一节讲述的基础类型,还拥有很多复杂的高级类型。高级类型基本是通过组合这些基础类型得到的,可以帮助开发人员解决工程中很多复杂棘手的问题,本节仅介绍几种常见的高级类型。
索引类型(Index Types)是对象类型的高级变种。通过使用索引类型,TypeScript编译器能够检查使用了动态属性名的代码,在实际开发中一般会结合索引类型查询和索引访问操作符使用。
![](https://epubservercos.yuewen.com/211D7B/26763692509344406/epubprivate/OEBPS/Images/43579_38_3.jpg?sign=1734435636-kSx7cfUx24Eg4xEmwcMG6skiDJbMZqfI-0-e02fdcda259c16eda0677d95dd7704dd)
![](https://epubservercos.yuewen.com/211D7B/26763692509344406/epubprivate/OEBPS/Images/43579_39_1.jpg?sign=1734435636-rL8OaeUSmc8q63DCNyyUkMcm4fdeVhpU-0-0368fdeef1fb4d1c62a9353dc939161b)
交叉类型(Intersection Types)将多个类型合并为一个类型,用“&”来分隔每个类型。开发人员可以使用交叉类型把现有的多种类型叠加到一起成为一种包含所有类型特性的新类型,这种灵活的开发方式将大大改善开发体验。例如,一个人拥有姓名、年龄等人格属性,当他学会游泳以后就可以成为一名游泳者,同时具有这两种属性。
![](https://epubservercos.yuewen.com/211D7B/26763692509344406/epubprivate/OEBPS/Images/43579_39_2.jpg?sign=1734435636-4Pm8MMk8ZUmoIBQ8lb54A0bmdaqXxp47-0-8d627cb18e1d94e7601d64d1b034046d)
联合类型(Union Types)表示一个值可以是几种类型之一,用“|”来分隔每个类型。如果一个变量是联合类型,那么TypeScript允许该变量访问此联合类型的所有类型里的共有成员。
![](https://epubservercos.yuewen.com/211D7B/26763692509344406/epubprivate/OEBPS/Images/43579_39_3.jpg?sign=1734435636-lawjzooafXTA67O8WWZsveVdbzKnXUmu-0-1802ea3235a569c998fe3cabb0647d2d)
![](https://epubservercos.yuewen.com/211D7B/26763692509344406/epubprivate/OEBPS/Images/43579_40_1.jpg?sign=1734435636-wgL8ioWmjsc8XRolvJHbj9dpKOCmSqP2-0-55a9ff0dabd4901dfe0a30a56f03ea14)
映射类型(Mappod Types)是通过转换旧类型中的每个属性来创建新类型的方式。
![](https://epubservercos.yuewen.com/211D7B/26763692509344406/epubprivate/OEBPS/Images/43579_40_2.jpg?sign=1734435636-J1aMjSDY4WRgPxez6QXD7FpyutbfEAxk-0-0e052cead6913dc7eb0f943dd9701621)
除了以上这些高级类型,开源社区还有一个名为utility-types的TypeScript类型操作工具集,它提供了许多自定义的高阶类型来帮助开发人员进行类型定义。
2.1.4 项目配置
首先需要使用NPM包管理工具全局安装TypeScript。
![](https://epubservercos.yuewen.com/211D7B/26763692509344406/epubprivate/OEBPS/Images/43579_40_3.jpg?sign=1734435636-xxm1kWytLWbGlkylQ7ib4od5NrUjrY8G-0-af3003c44c75f7c530e0aae819a5a228)
在本地新建一个名为buy.ts的文件,输入一些简单的代码。
![](https://epubservercos.yuewen.com/211D7B/26763692509344406/epubprivate/OEBPS/Images/43579_41_1.jpg?sign=1734435636-MCe8c3w2AAvhOftYFzIVrariAadTw5m8-0-cacb4e39ec424c2de59687ee47cbcfb9)
然后使用tsc进行编译,编译器先对类型进行检测,如果类型检测成功通过,那么它会将类型相关的代码删除,以便减小代码体积。
![](https://epubservercos.yuewen.com/211D7B/26763692509344406/epubprivate/OEBPS/Images/43579_41_2.jpg?sign=1734435636-lIrP2xeDcvmzuCMvrK1cVJeCDxRHEVfz-0-90a5f37d9848189999d57e0d79e39120)
如果类型检测失败,那么编译器会在控制台抛出对应的错误提示信息,并给出具体的错误信息,包括文件路径、错误行列信息、错误原因等,以便帮助开发人员快速排查定位。例如,修改上述代码的类型描述。
![](https://epubservercos.yuewen.com/211D7B/26763692509344406/epubprivate/OEBPS/Images/43579_41_3.jpg?sign=1734435636-ME8xJvoFHr0Ak78fauEQsDQGgIff7EDt-0-c336bbb27c630bf9e0992efc1ec963cf)
编译器将在控制台给出以下提示:
![](https://epubservercos.yuewen.com/211D7B/26763692509344406/epubprivate/OEBPS/Images/43579_41_4.jpg?sign=1734435636-XtZUS5vklPuCG1lPNHQf5TKzYnBVZuKJ-0-9bdb41608638fadc5f75afe764be1978)
在实际项目开发中,不同的团队和业务在使用TypeScript进行编译时通常会有不同的诉求,此时开发人员可以使用官方提供的tsconfig.json文件进行自定义配置。在项目的根目录下创建一个tsconfig.json文件,当tsc运行时,TypeScript会在当前目录或父级目录中寻找tsconfig.json文件。
开发人员可以对compilerOptions字段进行自定义配置,常用的配置项对应的功能描述如下。
![](https://epubservercos.yuewen.com/211D7B/26763692509344406/epubprivate/OEBPS/Images/43579_42_1.jpg?sign=1734435636-Y8MZwvwTnwh9Pd5fWs3TjKQv3glLTuRE-0-e11b37610021a66d631b204fa9955c0c)
![](https://epubservercos.yuewen.com/211D7B/26763692509344406/epubprivate/OEBPS/Images/43579_43_1.jpg?sign=1734435636-0Pco3af6GhdNJB0wQh12ly11Xp3GaChs-0-f38d53f35d3f9053f789f7dc8dd989b7)
如果只需要定向编译某些文件,那么可以通过files属性进行配置。
![](https://epubservercos.yuewen.com/211D7B/26763692509344406/epubprivate/OEBPS/Images/43579_43_2.jpg?sign=1734435636-Ws5CqLSQD4znn95xYij6epWljtu0iGz8-0-f20d55f08c3a06ec061d85ded2a71f9f)
files不足以满足复杂的路径匹配诉求,所以TypeScript还提供了include和exclude两个配置选项。include用来指定需要包含的文件,exclude则用来指定需要排除的文件,开发人员可以配置glob语法进行模糊匹配,从而自定义需要编译和排除的文件目录,进一步提高编译速度。
![](https://epubservercos.yuewen.com/211D7B/26763692509344406/epubprivate/OEBPS/Images/43579_43_3.jpg?sign=1734435636-n9kppYcpwAcQNiXDkcNxEDOxfhYNSuhY-0-a4e64f157b12c3edfe111310522c1da7)
当项目的TypeScript配置需要具备多层级关系时,开发人员可以通过extends继承上一级配置,再进行自定义修改。
![](https://epubservercos.yuewen.com/211D7B/26763692509344406/epubprivate/OEBPS/Images/43579_43_4.jpg?sign=1734435636-JPN1w5SIqiFaK5aLdiwBUrTz8cNplcOk-0-cc8455af68b572e92bafa435f5e1ac44)
结合以上基础配置描述,开发人员可以快速搭建一个简单的TypeScript项目,更多知识点和配置项可以访问相关网站进行学习。