这是第 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); // momoperson.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 对象蕴含参数符合要求的状况下,才返回 urlfunction validReqParams(request: unknown): request is ReqParams { return request && request.url}
开发小技巧
- 须要间断判断某个对象外面是否存在某个深层次的属性,能够应用
?.
if(result && result.data && result.data.list) // JSif(result?.data?.list) // TS
- 联结判断是否为空值,能够应用
??
let temp = (val !== null && val !== void 0 ? val : '1'); // JSlet 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