共计 7734 个字符,预计需要花费 20 分钟才能阅读完成。
这是第 84 篇不掺水的原创,想获取更多原创好文,请搜寻公众号关注咱们吧~ 本文首发于政采云前端博客:编写高质量可保护的代码:Awesome TypeScript
前言
高质量可保护的代码应具备可读性高、构造清晰、低耦合、易扩大等特点。而原生的 JavaScript 因为其弱类型和没有模块化的毛病,不利于大型利用的开发和保护,因而,TypeScript 也就应运而生。
TypeScript 是 JavaScript 的一个超集,它的设计初衷并不是为了代替 JavaScript,而是基于 JavaScript 做了一系列的加强,包含减少了动态类型、接口、类、泛型、办法重载等等。所以,只有你有肯定的 JavaScript 功底,那么 TypeScript 上手就非常简单。并且,你能够在 TypeScript 中欢快的应用 JavaScript 语法。
接下去,本文将给大家分享下,TypeScript 的重要个性以及在理论场景中的应用技巧,帮忙大家更高效的编写高质量可保护的代码。
Typescript VS Javascript
JavaScript
- JavaScript 是动静类型语言,在代码编译阶段不会对变量进行类型检测,从而会把潜在的类型谬误带到代码执行阶段。并且在遇到不同类型变量的赋值时,会主动进行类型转换,带来了不确定性,容易产生 bug。
- JavaScript 原生没有命名空间,须要手动创立命名空间,来进行模块化。并且,JavaScript 容许同名函数的反复定义,前面的定义能够笼罩后面的定义。这也给咱们开发和保护大型利用带来了不便。
TypeScript
-
TypeScript 是动态类型语言,通过类型注解提供编译时的动态类型查看。
- 在代码编译阶段会进行变量的类型检测,提前裸露潜在的类型谬误问题。并且在代码执行阶段,不容许不同类型变量之间的赋值。
- 清晰的类型注解,不仅让代码的可读性更好,同时也加强了 IDE 的能力,包含代码补全、接口提醒、跳转到定义等等。
- TypeScript 减少了模块类型,自带命名空间,不便了大型利用的模块化开发。
- TypeScript 的设计一种齐全面向对象的编程语言,具备模块、接口、类、类型注解等,能够让咱们的代码组织构造更清晰。
通过上述比照,能够看到 TypeScript 的呈现很好的补救了 JavaScript 的局部设计缺点,给咱们带来了很大的便当,也进步了代码的健壮性和扩展性。
重要个性
数据类型
-
根底数据类型包含:Boolean、Number、String、Array、Enum、Any、Unknown、Tuple、Void、Null、Undefined、Never。上面抉择几个 TypeScript 特有的类型进行详解:
- Enum 枚举:在编码过程中,要防止应用硬编码,如果某个常量是能够被一一列举进去的,那么就倡议应用枚举类型来定义,能够让代码更易保护。
// 包含 数字枚举、字符串枚举、异构枚举(数字和字符串的混合)。// 数字枚举在不设置默认值的状况下,默认第一个值为 0,其余顺次自增长 enum STATUS { PENDING, PROCESS, COMPLETED, } let status: STATUS = STATUS.PENDING; // 0
- Any 类型:不倡议应用。Any 类型为顶层类型,所有类型都能够被视为 any 类型,应用 Any 也就等同于让 TypeScript 的类型校验机制生效。
-
Unknown 类型:Unknown 类型也是顶层类型,它能够接管任何类型,但它与 Any 的区别在于,它首次赋值后就确定了数据类型,不容许变量的数据类型进行二次变更。所以,在须要接管所有类型的场景下,优先思考用 Unknown 代替 Any。
- Tuple 元组:反对数组内存储不同数据类型的元素,让咱们在组织数据的时候更灵便。
let tupleType: [string, boolean]; tupleType = ["momo", true];
- Void 类型:当函数没有返回值的场景下,通常将函数的返回值类型设置为 void。
类型注解
- TypeScript 通过类型注解提供编译时的动态类型查看,能够在编译阶段就发现潜在 Bug,同时让编码过程中的提醒也更智能。应用形式很简略,在
:
冒号前面注明变量的类型即可。
const str: string = 'abc';
接口
- 在面向对象编程的语言外面,接口是实现程序解耦的要害,它只定义具体蕴含哪些属性和办法,而不波及任何具体的实现细节。接口是基于类之上,更进一步对实体或行为进行形象,会让程序具备更好的扩展性。
-
利用场景:比方咱们在实现订单相干性能的时候,须要对订单进行形象,定义一个订单的接口,包含订单根本信息以及对订单的相干操作,而后基于这个接口来做进一步的实现。后续如果订单的相干操作性能有变动,只须要从新定义一个类来实现这个接口即可。
interface Animal { name: string; getName(): string;} class Monkey implements Padder {constructor(private name: string) {getName() {return 'Monkey:' + name;} } }
类
- TypeScript 的类除了包含最根本的属性和办法、getter 和 setter、继承等个性,还新增了公有字段。公有字段不能在蕴含的类之外拜访,甚至不能被检测到。Javascript 的类中是没有公有字段的,如果想模仿公有字段的话,必须要用闭包来模仿。上面用一些示例来阐明下类的应用:
-
属性和办法
class Person { // 动态属性 static name: string = "momo"; // 成员属性 gender: string; // 构造函数 constructor(str: string) {this.gender = str;} // 静态方法 static getName() {return this.name;} // 成员办法 getGender() {return 'Gender:' + this.gender;} } let person = new Person("female");
-
getter 和 setter
- 通过 getter 和 setter 办法来实现数据的封装和有效性校验,避免出现异常数据。
class Person { private _name: string; get name(): string {return this._name;} set name(newName: string) {this._name = newName;} } let person = new Person('momo'); console.log(person.name); // momo person.name = 'new_momo'; console.log(person.name); // new_momo
-
继承
class Animal { name: string; constructor(nameStr=:string) {this.name = nameStr;} move(distanceInMeters: number = 0) {console.log(`${this.name} moved ${distanceInMeters}m.`); } } class Snake extends Animal {constructor(name: string) {super(name); } move(distanceInMeters = 5) {super.move(distanceInMeters); } } let snake = new Snake('snake'); snake.move(); // 输入:'snake moved 5m'
-
公有字段
- 公有字段以
#
字符结尾。公有字段不能在蕴含的类之外拜访,甚至不能被检测到。
class Person { #name: string; constructor(name: string) {this.#name = name;} greet() {console.log(`Hello, ${this.#name}!`); } } let person = new Person('momo'); person.#name; // 拜访会报错
- 公有字段以
泛型
- 利用场景:当咱们须要思考代码的可复用性时,就须要用到泛型。让组件不仅可能反对以后的数据类型,同时也能反对将来的数据类型。泛型容许同一个函数承受不同类型参数,相比于应用 Any 类型,应用泛型来创立的组件可复用和易扩展性要更好,因为泛型会保留参数类型。泛型能够利用于接口、类、变量。上面用一些示例来阐明下泛型的应用:
-
泛型接口
interface identityFn<T> {(arg: T): T; }
-
泛型类
class GenericNumber<T> { zeroValue: T; add: (x: T, y: T) => T; } let myGenericNumber = new GenericNumber<number>(); myGenericNumber.zeroValue = 0; myGenericNumber.add = function (x, y) {return x + y;};
-
泛型变量
应用大写字母 A-Z 定义的类型变量都属于泛型,常见泛型变量如下:
- T(Type):示意一个 TypeScript 类型
- K(Key):示意对象中的键类型
- V(Value):示意对象中的值类型
- E(Element):示意元素类型
穿插类型
-
穿插类型就是将多个类型合并为一个类型。通过
&
运算符定义。如下示例中,将 Person 类型和 Company 类型合并后,生成了新的类型 Staff,该类型同时具备这两种类型的所有成员。interface Person { name: string; gender: string; } interface Company {companyName: string;} type Staff = Person & Company; const staff: Staff = { name: 'momo', gender: 'female', companyName: 'ZCY' };
联结类型
-
联结类型就是由具备或关系的多个类型组合而成,只有满足其中一个类型即可。通过
|
运算符定义。如下示例中,函数的入参为 string 或 number 类型即可。function fn(param: string | number): void {console.log("This is the union type"); }
类型爱护
-
类型爱护就是在咱们曾经辨认到以后数据是某种数据类型的状况下,平安的调用这个数据类型对应的属性和办法。罕用的类型爱护包含
in
类型爱护、typeof
类型爱护、instanceof
类型爱护和自定义
类型爱护。具体见以下示例:-
in
类型爱护interface Person { name: string; gender: string; } interface Employee { name: string; company: string; } type UnknownStaff = Person | Employee; function getInfo(staff: UnknownStaff) {if ("gender" in staff) {console.log("Person info"); } if ("company" in staff) {console.log("Employee info"); } }
-
typeof
类型爱护function processData(param: string | number): unknown {if (typeof param === 'string') {return param.toUpperCase() } return param; }
instanceof
类型爱护:和typeof
类型用法类似,它次要是用来判断是否是一个类的对象或者继承对象的。
function processData(param: Date | RegExp): unknown {if (param instanceof Date) {return param.getTime(); } return param; }
自定义
类型爱护:通过类型谓词parameterName is Type
来实现自定义类型爱护。如下示例,实现了接口的申请参数的类型爱护。
interface ReqParams { url: string; onSuccess?: () => void; onError?: () => void;} // 检测 request 对象蕴含参数符合要求的状况下,才返回 url function validReqParams(request: unknown): request is ReqParams {return request && request.url}
-
开发小技巧
- 须要间断判断某个对象外面是否存在某个深层次的属性,能够应用
?.
if(result && result.data && result.data.list) // JS
if(result?.data?.list) // TS
- 联结判断是否为空值,能够应用
??
let temp = (val !== null && val !== void 0 ? val : '1'); // JS
let temp = val ?? '1'; // TS
-
不要齐全依赖于类型查看,必要时还是须要编写兜底的防御性代码。
- 因为类型报错不会影响代码生成和执行,所以原则上还是会存在 fn(‘str’) 调用的可能性,所以须要 default 进行兜底的防御性代码。
function fn(value:boolean){switch(value){
case true:
console.log('true');
break;
case false:
console.log('false');
break;
default:
console.log('dead code');
}
}
- 对于函数,要严格控制返回值的类型.
// 举荐写法
function getLocalStorage<T>(key: string): T | null {const str = window.localStorage.getItem(key);
return str ? JSON.parse(str) : null;
}
const data = getLocalStorage<DataType>("USER_KEY");
-
利用 new() 实现工厂模式
- TypeScript 语法实现工厂模式很简略,只需先定义一个函数,并申明一个构造函数的类型参数,而后在函数体外面返回 c 这个类结构进去的对象即可。以下示例中,工厂函数结构进去的是 T 类型的对象。
function create<T>(c: { new(): T }): T {return new c();
}
class Test {constructor() {}}
create(Test);
- 优先思考应用 Unknown 类型而非 Any
- 应用 readonly 标记入参,保障参数不会在函数内被批改
function fn(arr:readonly number[] ){
let sum=0, num = 0;
while((num = arr.pop()) !== undefined){sum += num;}
return sum;
}
- 应用 Enum 保护常量表,实现更平安的类型查看
// 应用 const enum 保护常量
const enum PROJ_STATUS {
PENDING = 'PENDING',
PROCESS = 'PROCESS',
COMPLETED = 'COMPLETED'
}
function handleProject (status: PROJ_STATUS): void {
}
handleProject(PROJ_STATUS.COMPLETED)
- 倡议开启以下编译查看选项,便于在编译环境发现潜在 Bug
{
"compilerOptions": {
/* 严格的类型查看选项 */
"strict": true, // 启用所有严格类型查看选项
"noImplicitAny": true, // 在表达式和申明上有隐含的 any 类型时报错
"strictNullChecks": true, // 启用严格的 null 查看
"noImplicitThis": true, // 当 this 表达式值为 any 类型的时候,生成一个谬误
"alwaysStrict": true, // 以严格模式查看每个模块,并在每个文件里退出 'use strict'
/* 额定的查看 */
"noUnusedLocals": true, // 有未应用的变量时,抛出谬误
"noUnusedParameters": true, // 有未应用的参数时,抛出谬误
"noImplicitReturns": true, // 并不是所有函数里的代码都有返回值时,抛出谬误
"noFallthroughCasesInSwitch": true,// 报告 switch 语句的 fallthrough 谬误。(即,不容许 switch 的 case 语句贯通)}
}
相干 VSCode 插件举荐
TypeScript Extension Pack,它汇合了咱们日常罕用的 TypeScript 相干插件:
- TSLint:自动检测和修复不符合规范的 TypeScript 代码。
- TypeScript Hero:对 import 引入模块程序进行排序和组织,移除未被应用的。MacOS 上快捷键
Ctrl+Opt+o
,Win/Linux 上快捷键Ctrl+Alt+o
。 - json2ts:将剪切板中的 JSON 转化成 TypeScript 接口。MacOS 上快捷键
Ctrl+Opt+V
,Win/Linux 上快捷键Ctrl+Alt+V
。 - Move TS:在挪动 TypeScript 文件或者蕴含 TypeScript 文件的文件夹时,会自动更新相干依赖模块的 import 门路。
- Path Intellisense:门路和文件名的主动提醒补全性能。
- TypeScript Importer:import 引入模块时,主动搜寻以后 workspace 下所有 export 的模块,并主动进行提醒补全。
- Prettier – Code formatter:格式化代码。
招贤纳士
政采云前端团队(ZooTeam),一个年老富裕激情和创造力的前端团队,隶属于政采云产品研发部,Base 在风景如画的杭州。团队现有 40 余个前端小伙伴,平均年龄 27 岁,近 3 成是全栈工程师,妥妥的青年风暴团。成员形成既有来自于阿里、网易的“老”兵,也有浙大、中科大、杭电等校的应届新人。团队在日常的业务对接之外,还在物料体系、工程平台、搭建平台、性能体验、云端利用、数据分析及可视化等方向进行技术摸索和实战,推动并落地了一系列的外部技术产品,继续摸索前端技术体系的新边界。
如果你想扭转始终被事折腾,心愿开始能折腾事;如果你想扭转始终被告诫须要多些想法,却无从破局;如果你想扭转你有能力去做成那个后果,却不须要你;如果你想扭转你想做成的事须要一个团队去撑持,但没你带人的地位;如果你想扭转既定的节奏,将会是“5 年工作工夫 3 年工作教训”;如果你想扭转原本悟性不错,但总是有那一层窗户纸的含糊… 如果你置信置信的力量,置信平凡人能成就不凡事,置信能遇到更好的本人。如果你心愿参加到随着业务腾飞的过程,亲手推动一个有着深刻的业务了解、欠缺的技术体系、技术发明价值、影响力外溢的前端团队的成长历程,我感觉咱们该聊聊。任何工夫,等着你写点什么,发给 ZooTeam@cai-inc.com