一、TypeScript 概述
- TypeScript 是微软开发的自由和开源的编程语言;
- 是 JavaScript 的一个超集:本质上向这个语言,添加了可选的静态类型,和基于类的面向对象编程;
- TypeScript 提供新的 JavaScript 特性,包括 2015 年的 ECMAScript 中的特性,比如:异步功能和 Decorators,以帮助建立健壮的组件;
- TypeScript 与 ES5、ES2015 和 ES2016 之间的关系:
1. TypeScript 与 JavaScript 的区别
TypeScript | JavaScript |
---|---|
JavaScript 的超集,解决代码复杂性 | 脚本语言,用于创建动态网页 |
可以在编译期间发现并纠正错误 | 解释型语言,只能在运行时发现错误 |
强类型,支持静态和动态类型 | 弱类型,没有静态类型选项 |
最终编译成 JS 代码,浏览器才可使用 | 可以直接在浏览器中使用 |
支持模块、泛型和接口 | 不支持模块,泛型或接口 |
社区的支持仍在增长,还不是很大 | 大量的社区支持以及大量文档和解决问题的支持 |
2. 获取 TypeScript
命令行的 TypeScript 编译器,使用 npm 包管理器安装:
- 安装:
$ npm install -g typescript
- 验证:
$ tsc -v
# Version 4.0.2
- 编译 TypeScript 文件:
$ tsc hello.ts
# hello.ts => hello.js
也可以直接使用线工具 TypeScript Playground ,配置
TS Config
的 Target,设置不同的编译目标,生成不同的目标代码。下图编译目标 ES5:
3. TypeScript 工作流程
- TypeScript 编译器,根据配置的选项,编译成 3 个 js 文件;
- TypeScript 的 Web 项目,还需对生成的 js 文件进行 先打包 处理,然后 再进行部署。
4. TypeScript 测试
- 新建
hello.ts
文件:
function greet(person: string) {
return 'Hello, ' + person;
}
console.log(greet("TypeScript"));
- 命令行执行
tsc hello.ts
命令,会生成一个编译好的文件hello.js
:
"use strict";
function greet(person) {
return 'Hello, ' + person;
}
console.log(greet("TypeScript"));
- 编译生成的 js 文件,
person
参数的类型信息没有了。 - TypeScript 只会在 编译阶段 对类型进行 静态检查,有错误,编译时就会报错。
- 运行时,编译生成的 js 与普通的 JavaScript 文件一样,并 不会进行类型检查。
二、TypeScript 基础类型
1. Boolean 类型
let isDone: boolean = false;
// ES5:var isDone = false;
2. Number 类型
let count: number = 10;
// ES5:var count = 10;
3.String 类型
let name: string = "semliker";
// ES5:var name = 'semlinker';
4. Symbol 类型
let sym = Symbol();
let obj = {
[sym]: "value"
};
console.log(obj[sym]); // "value"
5. Array 类型
let list: number[] = [1, 2, 3];
// ES5:var list = [1,2,3];
let list: Array = [1, 2, 3]; // Array泛型语法
// ES5:var list = [1,2,3];
6. Enum 类型
- 枚举可以定义带名字的常量;
- 使用枚举可以清晰地表达意图,或创建一组有区别的用例;
- TypeScript 支持数字的和基于字符串的枚举。
- 数字枚举
enum Direction {
NORTH,
SOUTH,
EAST,
WEST,
}
let dir: Direction = Direction.NORTH;
默认:NORTH 的初始值为 0,其余的成员会从 1 开始自动增长;
Direction.SOUTH
的值为 1,Direction.EAST
的值为 2,Direction.WEST
的值为 3。编译后,ES5 代码如下:
var Direction;
(function (Direction) {
Direction[Direction["NORTH"] = 0] = "NORTH";
Direction[Direction["SOUTH"] = 1] = "SOUTH";
Direction[Direction["EAST"] = 2] = "EAST";
Direction[Direction["WEST"] = 3] = "WEST";
})(Direction || (Direction = {}));
var dir = Direction.NORTH;
- 也可以设置 NORTH 的初始值,比如:
enum Direction {
NORTH = 3,
SOUTH,
EAST,
WEST,
}
- 字符串枚举
- TypeScript 2.4 版本,允许使用字符串枚举;
- 在一个字符串枚举里,每个成员都必须用字符串字面量,或另外一个字符串枚举成员进行初始化。
enum Direction {
NORTH = "NORTH",
SOUTH = "SOUTH",
EAST = "EAST",
WEST = "WEST",
}
- 编译后,ES5 代码如下:
var Direction;
(function (Direction) {
Direction["NORTH"] = "NORTH";
Direction["SOUTH"] = "SOUTH";
Direction["EAST"] = "EAST";
Direction["WEST"] = "WEST";
})(Direction || (Direction = {}));
- 数字枚举除了支持 从成员名称到成员值 的普通映射之外,它还支持 从成员值到成员名称 的反向映射:
enum Direction {
NORTH,
SOUTH,
EAST,
WEST,
}
let dirName = Direction[0]; // NORTH
let dirVal = Direction["NORTH"]; // 0
- 纯字符串枚举,不能省略任何初始化程序;
- 数字枚举,没有显式设置值时,会使用默认规则进行初始化。
- 常量枚举
- 常量枚举:使用
const
关键字修饰的枚举; - 常量枚举会使用内联语法,不会为枚举类型编译生成任何 JavaScript。
const enum Direction {
NORTH,
SOUTH,
EAST,
WEST,
}
let dir: Direction = Direction.NORTH;
- 编译后,ES5 代码如下:
var dir = 0 /* NORTH */;
- 异构枚举
异构枚举的成员值是数字和字符串的混合:
enum Enum {
A,
B,
C = "C",
D = "D",
E = 8,
F,
}
- 编译后,ES5 代码如下:
var Enum;
(function (Enum) {
Enum[Enum["A"] = 0] = "A";
Enum[Enum["B"] = 1] = "B";
Enum["C"] = "C";
Enum["D"] = "D";
Enum[Enum["E"] = 8] = "E";
Enum[Enum["F"] = 9] = "F";
})(Enum || (Enum = {}));
- 数字枚举 相对字符串枚举多了 反向映射:
console.log(Enum.A) //输出:0
console.log(Enum[0]) // 输出:A
7. Any 类型
- TypeScript 中,任何类型都可以被归为 any 类型;
- any 类型成为了类型系统的顶级类型(全局超级类型)。
let notSure: any = 666;
notSure = "semlinker";
notSure = false;
-
any
类型本质上是类型系统的一个逃逸舱; - TypeScript 允许对
any
类型的值,执行任何操作,无需事先执行任何检查:
let value: any;
value.foo.bar; // OK
value.trim(); // OK
value(); // OK
new value(); // OK
value[0][1]; // OK
- 使用
any
类型,可以很容易地编写类型正确,但在运行时有问题的代码; - 使用
any
类型,就无法使用 TypeScript 提供的保护机制; - 为了解决
any
带来的问题,TypeScript 3.0 引入了unknown
类型。
8. Unknown 类型
- 所有类型都可以赋值给
unknown
; -
unknown
另一种顶级类型(一种是any
)。
let value: unknown;
value = true; // OK
value = 42; // OK
value = "Hello World"; // OK
value = []; // OK
value = {}; // OK
value = Math.random; // OK
value = null; // OK
value = undefined; // OK
value = new TypeError(); // OK
value = Symbol("type"); // OK
- 对
value
变量的所有赋值都被认为是类型正确的; - 将类型为
unknown
的值赋值给其他类型的变量。
let value: unknown;
let value1: unknown = value; // OK
let value2: any = value; // OK
let value3: boolean = value; // Error
let value4: number = value; // Error
let value5: string = value; // Error
let value6: object = value; // Error
let value7: any[] = value; // Error
let value8: Function = value; // Error
-
unknown
类型只能被赋值给any
类型和unknown
类型本身; - 只有能够保存任意类型值的容器,才能保存
unknown
类型的值。
let value: unknown;
value.foo.bar; // Error
value.trim(); // Error
value(); // Error
new value(); // Error
value[0][1]; // Error
- 将
value
变量类型设置为unknown
后,这些操作都不再被认为是类型正确的; - 通过将
any
类型改变为unknown
类型,将允许所有更改的默认设置,更改为禁止任何更改。
9. Tuple 类型
- 数组一般由同种类型的值组成;
- 元组:单个变量中存储不同类型的值;
- JavaScript 中是没有元组,元组是 TypeScript 中特有的类型,其工作方式类似于数组;
- 元组可用于定义具有,有限数量的未命名属性的类型,每个属性都有一个关联的类型;
- 使用元组时,必须提供每个属性的值。
let tupleType: [string, boolean];
tupleType = ["semlinker", true];
- 元组与数组一样,可以通过下标来访问:
console.log(tupleType[0]); // semlinker
console.log(tupleType[1]); // true
- 元组初始化时,类型不匹配时,会报错:
tupleType = [true, "semlinker"];
- 错误信息:类型不匹配
[0]: Type 'true' is not assignable to type 'string'.
[1]: Type 'string' is not assignable to type 'boolean'.
- 元组初始化时,必须提供每个属性的值,不然也会出现错误:
tupleType = ["semlinker"];
- 错误信息:
Property '1' is missing in type '[string]' but required in type '[string, boolean]'.
10. Void 类型
- void 类型是与 any 类型相反,表示没有任何类型;
- 函数没有返回值时,返回值类型是 void:
// 声明函数返回值为void
function warnUser(): void {
console.log("This is my warning message");
}
- 编译生成,ES5 代码如下:
function warnUser() {
console.log("This is my warning message");
}
- 注意:声明 void 类型的变量没有什么作用,值只能为
undefined
或null
:
let unusable: void = undefined;
11. Null 和 Undefined 类型
-
undefined
和null
各有各的类型:undefined
和null
。
let u: undefined = undefined;
let n: null = null;
- 默认
null
和undefined
是所有类型的子类型; - 可以把
null
和undefined
赋值给number
类型的变量。 - 如果指定了
--strictNullChecks
标记,null
和undefined
只能赋值给void
和它们各自的类型。
12. object、Object 和 {} 类型
- object 类型
- object 类型是:TypeScript 2.2 引入的新类型,用于表示 非原始类型。
// node_modules/typescript/lib/lib.es5.d.ts
interface ObjectConstructor {
create(o: object | null): any;
// ...
}
const proto = {};
Object.create(proto); // OK
Object.create(null); // OK
Object.create(undefined); // Error
Object.create(1337); // Error
Object.create(true); // Error
Object.create("oops"); // Error
- Object 类型
-
Object 类型:是所有 Object 类的实例的类型,由两个接口来定义:
- Object 接口;
- ObjectConstructor 接口。
Object 接口:定义了 Object.prototype 原型对象上的属性;
// node_modules/typescript/lib/lib.es5.d.ts
interface Object {
constructor: Function;
toString(): string;
toLocaleString(): string;
valueOf(): Object;
hasOwnProperty(v: PropertyKey): boolean;
isPrototypeOf(v: Object): boolean;
propertyIsEnumerable(v: PropertyKey): boolean;
}
- ObjectConstructor 接口:定义了 Object 类的属性。
// node_modules/typescript/lib/lib.es5.d.ts
interface ObjectConstructor {
/** Invocation via `new` */
new(value?: any): Object;
/** Invocation via function calls */
(value?: any): any;
readonly prototype: Object;
getPrototypeOf(o: any): any;
// ···
}
declare var Object: ObjectConstructor;
- Object 类的所有实例,都继承了 Object 接口中的所有属性。
- {} 类型
- {} 类型:一个没有成员的对象;
- 访问对象的属性时,产生编译时错误。
// Type {}
const obj = {};
// Error: Property 'prop' does not exist on type '{}'.
obj.prop = "semlinker";
- 可以使用在 Object 类型上,定义的所有属性和方法,可通过 JavaScript 的原型链隐式地使用:
// Type {}
const obj = {};
// "[object Object]"
obj.toString();
13. Never 类型
-
never
类型:永不存在的值的类型; - 如:函数表达式或箭头函数表达式的返回值类型。
// 返回never的函数必须存在无法达到的终点
function error(message: string): never {
throw new Error(message);
}
function infiniteLoop(): never {
while (true) {}
}
- 可以利用 never 类型的特性,来实现全面性检查:
type Foo = string | number;
function controlFlowAnalysisWithNever(foo: Foo) {
if (typeof foo === "string") {
// 这里 foo 被收窄为 string 类型
} else if (typeof foo === "number") {
// 这里 foo 被收窄为 number 类型
} else {
// foo 在这里是 never
const check: never = foo;
}
}
-
注意:
-
在 else 分支里面,把收窄为 never 的 foo,赋值给一个显示声明的 never 变量;
type Foo = string | number | boolean;
如果逻辑正确,这里能够编译通过,如果以后修改了 Foo 的类型,修改
controlFlowAnalysisWithNever
方法中的控制流程,这时 else 分支的 foo 类型,会被收窄为boolean
类型,导致无法赋值给 never 类型,这时就会产生一个编译错误;controlFlowAnalysisWithNever
方法总是穷尽了 Foo 的所有可能类型。
-
结论:使用 never 避免出现新增了联合类型,没有对应的实现,目的就是写出类型绝对安全的代码。
三、TypeScript 断言
1. 类型断言
类型断言:类似其他语言里的类型转换,但不进行特殊的数据检查和解构;
没有运行时的影响,只是在编译阶段起作用。
类型断言有两种形式:
尖括号 语法
let someValue: any = "this is a string";
let strLength: number = (someValue).length;
- as 语法
let someValue: any = "this is a string";
let strLength: number = (someValue as string).length;
2. 非空断言
无法断定类型时,操作符
!
可以用于断言操作,对象是null!
和undefined!
类型;**x! 将从 x 值域中,排除 null 和 undefined **。
-
非空断言什么用:
-
忽略 undefined 和 null 类型
function myFunc(maybeString: string | undefined | null) { // Type 'string | null | undefined' is not assignable to type 'string'. // Type 'undefined' is not assignable to type 'string'. const onlyString: string = maybeString; // Error const ignoreUndefinedAndNull: string = maybeString!; // Ok }
-
调用函数时忽略 undefined 类型
type NumGenerator = () => number; function myFunc(numGenerator: NumGenerator | undefined) { // Object is possibly 'undefined'.(2532) // Cannot invoke an object which is possibly 'undefined'.(2722) const num1 = numGenerator(); // Error const num2 = numGenerator!(); //OK }
-
!
非空断言操作符,会从编译生成的 JavaScript 代码中移除,使用时要注意。
const a: number | undefined = undefined;
const b: number = a!;
console.log(b);
- 编译生成, ES5 代码:
"use strict";
const a = undefined;
const b = a;
console.log(b);
- 生成的 ES5 代码中,
!
非空断言操作符被移除了,所以在浏览器中执行以上代码,在控制台会输出undefined
。
3. 确定赋值断言
- TypeScript 2.7 版本引入了确定赋值断言,即允许在实例属性和变量声明后面放置一个
!
号,从而告诉 TypeScript 该属性会被明确地赋值:
let x: number;
initialize();
// Variable 'x' is used before being assigned.(2454)
console.log(2 * x); // Error
function initialize() {
x = 10;
}
- 异常信息:变量 x 在赋值前被使用了,要解决该问题,可以使用确定赋值断言:
let x!: number;
initialize();
console.log(2 * x); // Ok
function initialize() {
x = 10;
}
- 通过
let x!: number;
确定赋值断言,TypeScript 编译器就会知道该属性会被明确地赋值。
四、类型守卫
- 类型保护:可执行运行时检查的一种表达式,用于确保该类型在一定的范围内;
- 类型保护可以保证,一个字符串是一个字符串,尽管它的值也可以是一个数值;
- 类型保护与特性检测并不是完全不同,其主要思想是尝试检测属性、方法或原型,以确定如何处理值;
- 主要有四种的方式来实现类型保护:
1. in 关键字
interface Admin {
name: string;
privileges: string[];
}
interface Employee {
name: string;
startDate: Date;
}
type UnknownEmployee = Employee | Admin;
function printEmployeeInformation(emp: UnknownEmployee) {
console.log("Name: " + emp.name);
if ("privileges" in emp) {
console.log("Privileges: " + emp.privileges);
}
if ("startDate" in emp) {
console.log("Start Date: " + emp.startDate);
}
}
2. typeof 关键字
function padLeft(value: string, padding: string | number) {
if (typeof padding === "number") {
return Array(padding + 1).join(" ") + value;
}
if (typeof padding === "string") {
return padding + value;
}
throw new Error(`Expected string or number, got '${padding}'.`);
}
-
typeof
类型保护只支持两种形式:typeof v === "typename"
typeof v !== typename
-
"typename"
必须是"number"
,"string"
,"boolean"
或"symbol"
; - TypeScript 并不会阻止与其它字符串比较,语言不会把那些表达式,识别为类型保护。
3. instanceof 关键字
interface Padder {
getPaddingString(): string;
}
class SpaceRepeatingPadder implements Padder {
constructor(private numSpaces: number) {}
getPaddingString() {
return Array(this.numSpaces + 1).join(" ");
}
}
class StringPadder implements Padder {
constructor(private value: string) {}
getPaddingString() {
return this.value;
}
}
let padder: Padder = new SpaceRepeatingPadder(6);
if (padder instanceof SpaceRepeatingPadder) {
// padder的类型收窄为 'SpaceRepeatingPadder'
}
4. 自定义类型保护的类型谓词
function isNumber(x: any): x is number {
return typeof x === "number";
}
function isString(x: any): x is string {
return typeof x === "string";
}
五、联合类型和类型别名
1. 联合类型
- 联合类型通常与
null
或undefined
一起使用:
const sayHello = (name: string | undefined) => {
/* ... */
};
- 这里
name
的类型是string | undefined
,可以将string
或undefined
的值传递给sayHello
函数。
sayHello("semlinker");
sayHello(undefined);
- 类型 A 和类型 B ,联合后的类型,是同时接受 A 和 B 值的类型;
- 联合类型,可能会遇到以下的用法:
let num: 1 | 2 = 1;
type EventNames = 'click' | 'scroll' | 'mousemove';
-
1
、2
或'click'
被称为字面量类型,用来约束取值,只能是某几个值中的一个。
2. 可辨识联合
TypeScript 可辨识联合(Discriminated Unions)类型,也称为代数数据类型或标签联合类型;
包含 3 个要点:可辨识、联合类型和类型守卫;
本质是结合联合类型和字面量类型,的一种类型保护方法;
如果一个类型是多个类型的联合类型,且多个类型含有一个公共属性,那么就可以利用这个公共属性,来创建不同的类型保护区块。
- 可辨识
- 要求联合类型中的每个元素,都含有一个单例类型属性:
enum CarTransmission {
Automatic = 200,
Manual = 300
}
interface Motorcycle {
vType: "motorcycle"; // discriminant
make: number; // year
}
interface Car {
vType: "car"; // discriminant
transmission: CarTransmission
}
interface Truck {
vType: "truck"; // discriminant
capacity: number; // in tons
}
- 在上述代码中,分别定义了
Motorcycle
、Car
和Truck
三个接口,在这些接口中都包含一个vType
属性,该属性被称为可辨识的属性,而其它的属性只跟特性的接口相关。
- 联合类型
- 基于前面定义了三个接口,可以创建一个
Vehicle
联合类型:
type Vehicle = Motorcycle | Car | Truck;
- 现在可以开始使用
Vehicle
联合类型,对于Vehicle
类型的变量,它可以表示不同类型的车辆。
- 类型守卫
- 定义一个
evaluatePrice
方法,该方法用于根据车辆的类型、容量和评估因子来计算价格:
const EVALUATION_FACTOR = Math.PI;
function evaluatePrice(vehicle: Vehicle) {
return vehicle.capacity * EVALUATION_FACTOR;
}
const myTruck: Truck = { vType: "truck", capacity: 9.5 };
evaluatePrice(myTruck);
- TypeScript 编译器将会提示以下错误信息:
Property 'capacity' does not exist on type 'Vehicle'.
Property 'capacity' does not exist on type 'Motorcycle
- 在 Motorcycle 接口中,并不存在
capacity
属性,而对于 Car 接口来说,它也不存在capacity
属性; - 可以使用类型守卫,来重构一下前面定义的
evaluatePrice
方法,重构后的代码如下:
function evaluatePrice(vehicle: Vehicle) {
switch(vehicle.vType) {
case "car":
return vehicle.transmission * EVALUATION_FACTOR;
case "truck":
return vehicle.capacity * EVALUATION_FACTOR;
case "motorcycle":
return vehicle.make * EVALUATION_FACTOR;
}
}
- 使用
switch
和case
运算符来实现类型守卫,从而确保在evaluatePrice
方法中,可以安全地访问vehicle
对象中的所包含的属性,来正确的计算该车辆类型所对应的价格。
3. 类型别名
- 类型别名:用来给一个类型起个新名字。
type Message = string | string[];
let greet = (message: Message) => {
// ...
};
六、交叉类型
- 交叉类型:将多个类型合并为一个类型;
- 通过
&
运算符,可以将现有的多种类型叠加到一起,成为一种类型,它包含了所需的所有类型的特性。
type PartialPointX = { x: number; };
type Point = PartialPointX & { y: number; };
let point: Point = {
x: 1,
y: 1
}
- 在上面代码中,先定义了
PartialPointX
类型,接着使用&
运算符,创建一个新的Point
类型,表示一个含有 x 和 y 坐标的点,然后定义了一个Point
类型的变量并初始化。
1. 同名基础类型属性的合并
- 假设在合并多个类型的过程中,刚好出现某些类型存在相同的成员,但对应的类型又不一致:
interface X {
c: string;
d: string;
}
interface Y {
c: number;
e: string
}
type XY = X & Y;
type YX = Y & X;
let p: XY;
let q: YX;
- 在上面的代码中,接口 X 和接口 Y 都含有一个相同的成员 c,但它们的类型不一致;
- 此时 XY 类型或 YX 类型中成员 c 的类型,是不是可以是
string
或number
类型呢?比如下面的例子:
p = { c: 6, d: "d", e: "e" };
q = { c: "c", d: "d", e: "e" };
- 为什么接口 X 和接口 Y 混入后,成员 c 的类型会变成
never
呢?这是因为混入后成员 c 的类型为string & number
,即成员 c 的类型既可以是string
类型又可以是number
类型。很明显这种类型是不存在的,所以混入后成员 c 的类型为never
。
2. 同名非基础类型属性的合并
- 在上面示例中,刚好接口 X 和接口 Y 中,内部成员 c 的类型,都是基本数据类型,如果是非基本数据类型,又会是什么情形:
interface D { d: boolean; }
interface E { e: string; }
interface F { f: number; }
interface A { x: D; }
interface B { x: E; }
interface C { x: F; }
type ABC = A & B & C;
let abc: ABC = {
x: {
d: true,
e: 'semlinker',
f: 666
}
};
console.log('abc:', abc);
- 代码成功运行后,控制台会输出以下结果:
- 在混入多个类型时,若存在相同的成员,且成员类型为非基本数据类型,那么是可以成功合并。
七、TypeScript 函数
1. TypeScript 函数与 JavaScript 函数的区别
TypeScript | JavaScript |
---|---|
含有类型 | 无类型 |
箭头函数 | 箭头函数(ES2015) |
函数类型 | 无函数类型 |
必填和可选参数 | 所有参数都是可选的 |
默认参数 | 默认参数 |
剩余参数 | 剩余参数 |
函数重载 | 无函数重载 |
2. 箭头函数
- 常见语法
myBooks.forEach(() => console.log('reading'));
myBooks.forEach(title => console.log(title));
myBooks.forEach((title, idx, arr) =>
console.log(idx + '-' + title);
);
myBooks.forEach((title, idx, arr) => {
console.log(idx + '-' + title);
});
2.使用示例
// 未使用箭头函数
function Book() {
let self = this;
self.publishDate = 2016;
setInterval(function () {
console.log(self.publishDate);
}, 1000);
}
// 使用箭头函数
function Book() {
this.publishDate = 2016;
setInterval(() => {
console.log(this.publishDate);
}, 1000);
}
3. 参数类型和返回类型
function createUserId(name: string, id: number): string {
return name + id;
}
4. 函数类型
let IdGenerator: (chars: string, nums: number) => string;
function createUserId(name: string, id: number): string {
return name + id;
}
IdGenerator = createUserId;
5. 可选参数及默认参数
// 可选参数
function createUserId(name: string, id: number, age?: number): string {
return name + id;
}
// 默认参数
function createUserId(
name: string = "semlinker",
id: number,
age?: number
): string {
return name + id;
}
- 在声明函数时,可以通过
?
号来定义可选参数,比如age?: number
这种形式; - 注意:可选参数要放在普通参数的后面,不然会导致编译错误。
6. 剩余参数
function push(array, ...items) {
items.forEach(function (item) {
array.push(item);
});
}
let a = [];
push(a, 1, 2, 3);
7. 函数重载
- 函数重载或方法重载,是使用相同名称和不同参数数量或类型,创建多个方法的一种能力。
function add(a: number, b: number): number;
function add(a: string, b: string): string;
function add(a: string, b: number): string;
function add(a: number, b: string): string;
function add(a: Combinable, b: Combinable) {
// type Combinable = string | number;
if (typeof a === 'string' || typeof b === 'string') {
return a.toString() + b.toString();
}
return a + b;
}
- 在以上代码中,为 add 函数提供了多个函数类型定义,从而实现函数的重载;
- 除了可以重载普通函数之外,还可以重载类中的成员方法;
- 方法重载:指在同一个类中方法同名,参数不同(参数类型不同、参数个数不同或参数个数相同时参数的先后顺序不同),调用时根据实参的形式,选择与它匹配的方法执行操作的一种技术;
- 类中成员方法满足重载的条件:在同一个类中,方法名相同且参数列表不同:
class Calculator {
add(a: number, b: number): number;
add(a: string, b: string): string;
add(a: string, b: number): string;
add(a: number, b: string): string;
add(a: Combinable, b: Combinable) {
if (typeof a === 'string' || typeof b === 'string') {
return a.toString() + b.toString();
}
return a + b;
}
}
const calculator = new Calculator();
const result = calculator.add('Semlinker', ' Kakuqo');
- 需要注意,当 TypeScript 编译器处理函数重载时,它会查找重载列表,尝试使用第一个重载定义,如果匹配就使用这个;
- 定义重载时,一定要把最精确的定义,放在最前面;
- 在 Calculator 类中,
add(a: Combinable, b: Combinable){ }
,并不是重载列表的一部分,因此对于 add 成员方法来说,只定义了四个重载方法。
八、TypeScript 数组
1. 数组解构
let x: number; let y: number; let z: number;
let five_array = [0,1,2,3,4];
[x,y,z] = five_array;
2. 数组展开运算符
let two_array = [0, 1];
let five_array = [...two_array, 2, 3, 4];
3. 数组遍历
let colors: string[] = ["red", "green", "blue"];
for (let i of colors) {
console.log(i);
}
九、TypeScript 对象
1. 对象解构
let person = {
name: "Semlinker",
gender: "Male",
};
let { name, gender } = person;
2. 对象展开运算符
let person = {
name: "Semlinker",
gender: "Male",
address: "Xiamen",
};
// 组装对象
let personWithAge = { ...person, age: 33 };
// 获取除了某些项外的其它项
let { name, ...rest } = person;
十、TypeScript 接口
- 在面向对象语言中,接口是对行为的抽象,而具体如何行动需要由类去实现;
- TypeScript 中的接口,可以对类的一部分行为,进行抽象,也可以对「对象的形状(Shape)」进行描述。
1. 对象的形状
interface Person {
name: string;
age: number;
}
let semlinker: Person = {
name: "semlinker",
age: 33,
};
2. 可选 | 只读属性
interface Person {
readonly name: string;
age?: number;
}
- 只读属性:用于限制只能在对象刚刚创建的时候修改其值;
- TypeScript 还提供了
ReadonlyArray
类型,它与Array
相似,只是把所有可变方法去掉了,可以确保数组创建后再也不能被修改。
let a: number[] = [1, 2, 3, 4];
let ro: ReadonlyArray = a;
ro[0] = 12; // error!
ro.push(5); // error!
ro.length = 100; // error!
a = ro; // error!
3. 任意属性
- 索引签名 :接口中除了包含必选和可选属性之外,还允许有其他的任意属性:
interface Person {
name: string;
age?: number;
[propName: string]: any;
}
const p1 = { name: "semlinker" };
const p2 = { name: "lolo", age: 5 };
const p3 = { name: "kakuqo", sex: 1 }
4. 接口与类型别名的区别
- Objects/Functions
接口和类型别名,都可以用来描述对象的形状或函数签名:
接口
interface Point {
x: number;
y: number;
}
interface SetPoint {
(x: number, y: number): void;
}
- 类型别名
type Point = {
x: number;
y: number;
};
type SetPoint = (x: number, y: number) => void;
- Other Types
- 与接口类型不同,类型别名可以用于一些其他类型,比如:原始类型、联合类型和元组:
// primitive
type Name = string;
// object
type PartialPointX = { x: number; };
type PartialPointY = { y: number; };
// union
type PartialPoint = PartialPointX | PartialPointY;
// tuple
type Data = [number, string];
- Extend
接口和类型别名,都能够被扩展,但语法不同;
接口和类型别名不是互斥的;
接口可以扩展类型别名,而反过来是不行的。
Interface extends interface
interface PartialPointX { x: number; }
interface Point extends PartialPointX {
y: number;
}
- Type alias extends type alias
type PartialPointX = { x: number; };
type Point = PartialPointX & { y: number; };
- Interface extends type alias
type PartialPointX = { x: number; };
interface Point extends PartialPointX { y: number; }
- Type alias extends interface
interface PartialPointX { x: number; }
type Point = PartialPointX & { y: number; };
- Implements
- 类可以以相同的方式,实现接口或类型别名,但类不能实现使用类型别名定义的联合类型:
interface Point {
x: number;
y: number;
}
class SomePoint implements Point {
x = 1;
y = 2;
}
type Point2 = {
x: number;
y: number;
};
class SomePoint2 implements Point2 {
x = 1;
y = 2;
}
type PartialPoint = { x: number; } | { y: number; };
// A class can only implement an object type or
// intersection of object types with statically known members.
class SomePartialPoint implements PartialPoint { // Error
x = 1;
y = 2;
}
- Declaration merging
- 与类型别名不同,接口可以定义多次,会被自动合并为单个接口。
interface Point { x: number; }
interface Point { y: number; }
const point: Point = { x: 1, y: 2 };
十一、TypeScript 类
1. 类的属性与方法
- 在 TypeScript 中,通过
Class
关键字来定义一个类:
class Greeter {
// 静态属性
static cname: string = "Greeter";
// 成员属性
greeting: string;
// 构造函数 - 执行初始化操作
constructor(message: string) {
this.greeting = message;
}
// 静态方法
static getClassName() {
return "Class name is Greeter";
}
// 成员方法
greet() {
return "Hello, " + this.greeting;
}
}
let greeter = new Greeter("world");
- 成员属性与静态属性,成员方法与静态方法区别,编译生成, ES5 代码:
"use strict";
var Greeter = /** @class */ (function () {
// 构造函数 - 执行初始化操作
function Greeter(message) {
this.greeting = message;
}
// 静态方法
Greeter.getClassName = function () {
return "Class name is Greeter";
};
// 成员方法
Greeter.prototype.greet = function () {
return "Hello, " + this.greeting;
};
// 静态属性
Greeter.cname = "Greeter";
return Greeter;
}());
var greeter = new Greeter("world");
2. ECMAScript 私有字段
- 在 TypeScript 3.8 版本,开始支持 ECMAScript 私有字段:
class Person {
#name: string;
constructor(name: string) {
this.#name = name;
}
greet() {
console.log(`Hello, my name is ${this.#name}!`);
}
}
let semlinker = new Person("Semlinker");
semlinker.#name;
// ~~~~~
// Property '#name' is not accessible outside class 'Person'
// because it has a private identifier.
-
与常规属性(使用
private
修饰符声明的属性)不同,私有字段要牢记以下规则:私有字段以
#
字符开头,称之为私有名称;每个私有字段名称,都唯一地限定于其包含的类;
不能在私有字段上使用 TypeScript 可访问性修饰符(如 public 或 private);
私有字段不能在包含的类之外访问,甚至不能被检测到。
3. 访问器
- 通过
getter
和setter
方法来实现数据的封装和有效性校验,防止出现异常数据:
let passcode = "Hello TypeScript";
class Employee {
private _fullName: string;
get fullName(): string {
return this._fullName;
}
set fullName(newName: string) {
if (passcode && passcode == "Hello TypeScript") {
this._fullName = newName;
} else {
console.log("Error: Unauthorized update of employee!");
}
}
}
let employee = new Employee();
employee.fullName = "Semlinker";
if (employee.fullName) {
console.log(employee.fullName);
}
4. 类的继承
- 继承(Inheritance)是一种联结类与类的层次模型,指的是一个类(称为子类、子接口)继承另外的一个类(称为父类、父接口)的功能,并可以增加它自己的新功能的能力;
- 继承是类与类或者接口与接口之间最常见的关系;
- 继承是一种 is-a 关系:
- 在 TypeScript 中,通过
extends
关键字来实现继承:
class Animal {
name: string;
constructor(theName: string) {
this.name = theName;
}
move(distanceInMeters: number = 0) {
console.log(`${this.name} moved ${distanceInMeters}m.`);
}
}
class Snake extends Animal {
constructor(name: string) {
super(name); // 调用父类的构造函数
}
move(distanceInMeters = 5) {
console.log("Slithering...");
super.move(distanceInMeters);
}
}
let sam = new Snake("Sammy the Python");
sam.move();
5. 抽象类
- 使用
abstract
关键字声明的类,称之为抽象类; - 抽象类不能被实例化,因为里面包含一个或多个抽象方法;
- 所谓的抽象方法,是指不包含具体实现的方法:
abstract class Person {
constructor(public name: string){}
abstract say(words: string) :void;
}
// Cannot create an instance of an abstract class.(2511)
const lolo = new Person(); // Error
- 抽象类不能被直接实例化,只能实例化实现了所有抽象方法的子类:
abstract class Person {
constructor(public name: string){}
// 抽象方法
abstract say(words: string) :void;
}
class Developer extends Person {
constructor(name: string) {
super(name);
}
say(words: string): void {
console.log(`${this.name} says ${words}`);
}
}
const lolo = new Developer("lolo");
lolo.say("I love ts!"); // lolo says I love ts!
6. 类方法重载
- 类的方法,也支持重载:
class ProductService {
getProducts(): void;
getProducts(id: number): void;
getProducts(id?: number) {
if(typeof id === 'number') {
console.log(`获取id为 ${id} 的产品信息`);
} else {
console.log(`获取所有的产品信息`);
}
}
}
const productService = new ProductService();
productService.getProducts(666); // 获取id为 666 的产品信息
productService.getProducts(); // 获取所有的产品信息
十二、TypeScript 泛型
- 在像 C# 和 Java 这样的语言中,可以使用泛型来创建可重用的组件,一个组件可以支持多种类型的数据。 这样用户就可以以自己的数据类型来使用组件。
- 设计泛型目的:在成员之间提供有意义的约束,这些成员可以是:类的实例成员、类的方法、函数参数和函数返回值;
- 泛型(Generics):允许同一个函数接受不同类型参数的一种模板;
- 相比于使用 any 类型,使用泛型来创建可复用的组件要更好,因为泛型会保留参数类型。
1. 泛型语法
-
:像传递参数一样,传递了想要用于特定函数调用的类型。
参考上面的图片,当调用
identity
,(1) Number
类型就像参数1
一样,它将在出现T
的任何位置填充该类型;图中
内部的T
被称为类型变量,它是希望传递给 identity 函数的类型占位符,同时它被分配给value
参数用来代替它的类型:此时T
充当的是类型,而不是特定的 Number 类型;其中
T
代表 Type,在定义泛型时通常用作第一个类型变量名称;-
实际上
T
可以用任何有效名称代替,除了T
之外,以下是常见泛型变量代表的意思:K(Key):表示对象中的键类型;
V(Value):表示对象中的值类型;
E(Element):表示元素类型。
并不是只能定义一个类型变量,可以引入希望定义的任何数量的类型变量;
比如引入一个新的类型变量
U
,用于扩展定义的identity
函数:
function identity (value: T, message: U) : T {
console.log(message);
return value;
}
console.log(identity(68, "Semlinker"));
- 除了为类型变量显式设定值之外,一种更常见的做法是,使编译器自动选择这些类型,从而使代码更简洁,可以完全省略尖括号:
function identity (value: T, message: U) : T {
console.log(message);
return value;
}
console.log(identity(68, "Semlinker"));
- 对于上述代码,编译器能够知道参数类型,并将它们赋值给 T 和 U,而不需要开发人员显式指定它们。
2. 泛型接口
interface GenericIdentityFn {
(arg: T): T;
}
3. 泛型类
class GenericNumber {
zeroValue: T;
add: (x: T, y: T) => T;
}
let myGenericNumber = new GenericNumber();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function (x, y) {
return x + y;
};
4. 泛型工具类型
- 为了方便开发者,TypeScript 内置了一些常用的工具类型,比如 Partial、Required、Readonly、Record 和 ReturnType 等;
- 这里只简单介绍 Partial 工具类型,相关的基础知识:
- typeof
-
typeof
操作符,可以用来获取一个变量声明或对象的类型。
interface Person {
name: string;
age: number;
}
const sem: Person = { name: 'semlinker', age: 33 };
type Sem= typeof sem; // -> Person
function toArray(x: number): Array {
return [x];
}
type Func = typeof toArray; // -> (x: number) => number[]
- keyof
-
keyof
操作符是在 TypeScript 2.1 版本引入的,用于获取某种类型的所有键,其返回类型是联合类型。
interface Person {
name: string;
age: number;
}
type K1 = keyof Person; // "name" | "age"
type K2 = keyof Person[]; // "length" | "toString" | "pop" | "push" | "concat" | "join"
type K3 = keyof { [x: string]: Person }; // string | number
- 在 TypeScript 中支持两种索引签名:数字索引和字符串索引;
interface StringArray {
// 字符串索引 -> keyof StringArray => string | number
[index: string]: string;
}
interface StringArray1 {
// 数字索引 -> keyof StringArray1 => number
[index: number]: string;
}
- 为了同时支持两种索引类型,就得要求数字索引的返回值必须是字符串索引返回值的子类;
- 当使用数值索引时,JavaScript 在执行索引操作时,会先把数值索引先转换为字符串索引,所以
keyof { [x: string]: Person }
的结果会返回string | number
。
- in
-
in
遍历枚举类型:
type Keys = "a" | "b" | "c"
type Obj = {
[p in Keys]: any
} // -> { a: any, b: any, c: any }
- infer
- 在条件类型语句中,可以用
infer
声明一个类型变量并且对它进行使用。
type ReturnType = T extends (
...args: any[]
) => infer R ? R : any;
- 以上代码中
infer R
就是声明一个变量,来承载传入函数签名的返回值类型; - 用它取到函数返回值的类型,方便之后使用。
- extends
- 定义的泛型继承某些类等,可以通过 extends 关键字添加泛型约束。
interface Lengthwise {
length: number;
}
function loggingIdentity(arg: T): T {
console.log(arg.length);
return arg;
}
- 现在这个泛型函数被定义了约束,因此它不再是适用于任意类型:
loggingIdentity(3); // Error, number doesn't have a .length property
这时我们需要传入符合约束类型的值,必须包含必须的属性:
loggingIdentity({length: 10, value: 3});
- Partial
-
Partial
的作用:将某个类型里的属性,全部变为可选项?
。
定义:
/**
* node_modules/typescript/lib/lib.es5.d.ts
* Make all properties in T optional
*/
type Partial = {
[P in keyof T]?: T[P];
};
在以上代码中,首先通过
keyof T
拿到T
的所有属性名,然后使用in
进行遍历,将值赋给P
,最后通过T[P]
取得相应的属性值;中间的
?
号,用于将所有属性变为可选。示例:
interface Todo {
title: string;
description: string;
}
function updateTodo(todo: Todo, fieldsToUpdate: Partial) {
return { ...todo, ...fieldsToUpdate };
}
const todo1 = {
title: "Learn TS",
description: "Learn TypeScript",
};
const todo2 = updateTodo(todo1, {
description: "Learn TypeScript Enum",
});
- 在上面的
updateTodo
方法中,利用Partial
工具类型,定义fieldsToUpdate
的类型为Partial
,即:
{
title?: string | undefined;
description?: string | undefined;
}
十三、TypeScript 装饰器
1. 装饰器是什么
- 它是一个表达式
- 该表达式被执行后,返回一个函数
- 函数的入参分别为 target、name 和 descriptor
- 执行该函数后,可能返回 descriptor 对象,用于配置 target 对象
2. 装饰器的分类
类装饰器(Class decorators)
属性装饰器(Property decorators)
方法装饰器(Method decorators)
参数装饰器(Parameter decorators)
需要注意的是,若要启用实验性的装饰器特性,必须在命令行或
tsconfig.json
里启用experimentalDecorators
编译器选项:命令行:
tsc --target ES5 --experimentalDecorators
复制代码
- tsconfig.json:
{
"compilerOptions": {
"target": "ES5",
"experimentalDecorators": true
}
}
3. 类装饰器
- 类装饰器声明:
declare type ClassDecorator = (
target: TFunction
) => TFunction | void;
- 类装饰器:就是用来装饰类的,它接收一个参数:
- target: TFunction - 被装饰的类
function Greeter(target: Function): void {
target.prototype.greet = function (): void {
console.log("Hello Semlinker!");
};
}
@Greeter
class Greeting {
constructor() {
// 内部实现
}
}
let myGreeting = new Greeting();
myGreeting.greet(); // console output: 'Hello Semlinker!';
- 上面的例子中,定义了
Greeter
类装饰器,同时使用了@Greeter
语法糖,来使用装饰器; - 自定义输出:
function Greeter(greeting: string) {
return function (target: Function) {
target.prototype.greet = function (): void {
console.log(greeting);
};
};
}
@Greeter("Hello TS!")
class Greeting {
constructor() {
// 内部实现
}
}
let myGreeting = new Greeting();
myGreeting.greet(); // console output: 'Hello TS!';
4. 属性装饰器
- 属性装饰器声明:
declare type PropertyDecorator = (target:Object,
propertyKey: string | symbol ) => void;
-
属性装饰器顾名思义,用来装饰类的属性,它接收两个参数:
target: Object - 被装饰的类
propertyKey: string | symbol - 被装饰类的属性名
function logProperty(target: any, key: string) {
delete target[key];
const backingField = "_" + key;
Object.defineProperty(target, backingField, {
writable: true,
enumerable: true,
configurable: true
});
// property getter
const getter = function (this: any) {
const currVal = this[backingField];
console.log(`Get: ${key} => ${currVal}`);
return currVal;
};
// property setter
const setter = function (this: any, newVal: any) {
console.log(`Set: ${key} => ${newVal}`);
this[backingField] = newVal;
};
// Create new property with getter and setter
Object.defineProperty(target, key, {
get: getter,
set: setter,
enumerable: true,
configurable: true
});
}
class Person {
@logProperty
public name: string;
constructor(name : string) {
this.name = name;
}
}
const p1 = new Person("semlinker");
p1.name = "kakuqo";
- 以上代码定义了一个
logProperty
函数,来跟踪用户对属性的操作,当代码成功运行后,在控制台会输出以下结果:
Set: name => semlinker
Set: name => kakuqo
5. 方法装饰器
- 方法装饰器声明:
declare type MethodDecorator = (target:Object, propertyKey: string | symbol,
descriptor: TypePropertyDescript) => TypedPropertyDescriptor | void;
-
方法装饰器顾名思义,用来装饰类的方法,它接收三个参数:
target: Object - 被装饰的类
propertyKey: string | symbol - 方法名
descriptor: TypePropertyDescript - 属性描述符
function LogOutput(tarage: Function, key: string, descriptor: any) {
let originalMethod = descriptor.value;
let newMethod = function(...args: any[]): any {
let result: any = originalMethod.apply(this, args);
if(!this.loggedOutput) {
this.loggedOutput = new Array();
}
this.loggedOutput.push({
method: key,
parameters: args,
output: result,
timestamp: new Date()
});
return result;
};
descriptor.value = newMethod;
}
class Calculator {
@LogOutput
double (num: number): number {
return num * 2;
}
}
let calc = new Calculator();
calc.double(11);
// console ouput: [{method: "double", output: 22, ...}]
console.log(calc.loggedOutput);
6. 参数装饰器
- 参数装饰器声明:
declare type ParameterDecorator = (target: Object, propertyKey: string | symbol,
parameterIndex: number ) => void
-
参数装饰器顾名思义,是用来装饰函数参数,它接收三个参数:
target: Object - 被装饰的类
propertyKey: string | symbol - 方法名
parameterIndex: number - 方法中参数的索引值
function Log(target: Function, key: string, parameterIndex: number) {
let functionLogged = key || target.prototype.constructor.name;
console.log(`The parameter in position ${parameterIndex} at ${functionLogged} has
been decorated`);
}
class Greeter {
greeting: string;
constructor(@Log phrase: string) {
this.greeting = phrase;
}
}
// console output: The parameter in position 0
// at Greeter has been decorated
十四、TypeScript 4.0 新特性
1. 构造函数的类属性推断
- 当
noImplicitAny
配置属性被启用之后,TypeScript 4.0 就可以使用控制流分析来确认类中的属性类型:
class Person {
fullName; // (property) Person.fullName: string
firstName; // (property) Person.firstName: string
lastName; // (property) Person.lastName: string
constructor(fullName: string) {
this.fullName = fullName;
this.firstName = fullName.split(" ")[0];
this.lastName = fullName.split(" ")[1];
}
}
- 如果在 TypeScript 4.0 以前的版本,比如在 3.9.2 版本下,编译器会提示以下错误信息:
class Person {
// Member 'fullName' implicitly has an 'any' type.(7008)
fullName; // Error
firstName; // Error
lastName; // Error
constructor(fullName: string) {
this.fullName = fullName;
this.firstName = fullName.split(" ")[0];
this.lastName = fullName.split(" ")[1];
}
}
- 从构造函数推断类属性的类型,该特性带来了便利;
- 但在使用过程中,如果没法保证对成员属性都进行赋值,那么该属性可能会被认为是
undefined
。
class Person {
fullName; // (property) Person.fullName: string
firstName; // (property) Person.firstName: string | undefined
lastName; // (property) Person.lastName: string | undefined
constructor(fullName: string) {
this.fullName = fullName;
if(Math.random()){
this.firstName = fullName.split(" ")[0];
this.lastName = fullName.split(" ")[1];
}
}
}
2. 标记的元组元素
- 使用元组类型,来声明剩余参数的类型:
function addPerson(...args: [string, number]): void {
console.log(`Person info: name: ${args[0]}, age: ${args[1]}`)
}
addPerson("lolo", 5); // Person info: name: lolo, age: 5
- 对于上面的
addPerson
函数,也可以这样实现:
function addPerson(name: string, age: number) {
console.log(`Person info: name: ${name}, age: ${age}`)
}
- 这两种方式看起来没有多大的区别,但对于第一种方式,没法设置第一个参数和第二个参数的名称;
- 虽然这样对类型检查没有影响,但在元组位置上缺少标签,会使得它们难于使用;
- 为了提高开发者使用元组的体验,TypeScript 4.0 支持为元组类型设置标签:
function addPerson(...args: [name: string, age: number]): void {
console.log(`Person info: name: ${args[0]}, age: ${args[1]}`);
}
- 使用
addPerson
方法时,TypeScript 的智能提示就会变得更加友好。
// 未使用标签的智能提示
// addPerson(args_0: string, args_1: number): void
function addPerson(...args: [string, number]): void {
console.log(`Person info: name: ${args[0]}, age: ${args[1]}`)
}
// 已使用标签的智能提示
// addPerson(name: string, age: number): void
function addPerson(...args: [name: string, age: number]): void {
console.log(`Person info: name: ${args[0]}, age: ${args[1]}`);
}
十五、编译上下文
1. tsconfig.json 的作用
- 用于标识 TypeScript 项目的根路径;
- 用于配置 TypeScript 编译器;
- 用于指定编译的文件。
2. tsconfig.json 重要字段
- files - 设置要编译的文件的名称;
- include - 设置需要进行编译的文件,支持路径模式匹配;
- exclude - 设置无需进行编译的文件,支持路径模式匹配;
- compilerOptions - 设置与编译流程相关的选项。
3. compilerOptions 选项
- compilerOptions 支持很多选项,常见的有
baseUrl
、target
、baseUrl
、moduleResolution
和lib
等。 - compilerOptions 每个选项的详细说明如下:
{
"compilerOptions": {
/* 基本选项 */
"target": "es5",
// 指定 ECMAScript 目标版本: 'ES3' (default), 'ES5', 'ES6'/'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'
"module": "commonjs",
// 指定使用模块: 'commonjs', 'amd', 'system', 'umd' or 'es2015'
"lib": [],
// 指定要包含在编译中的库文件
"allowJs": true,
// 允许编译 javascript 文件
"checkJs": true,
// 报告 javascript 文件中的错误
"jsx": "preserve",
// 指定 jsx 代码的生成: 'preserve', 'react-native', or 'react'
"declaration": true,
// 生成相应的 '.d.ts' 文件
"sourceMap": true,
// 生成相应的 '.map' 文件
"outFile": "./",
// 将输出文件合并为一个文件
"outDir": "./",
// 指定输出目录
"rootDir": "./",
// 用来控制输出目录结构 --outDir.
"removeComments": true,
// 删除编译后的所有的注释
"noEmit": true,
// 不生成输出文件
"importHelpers": true,
// 从 tslib 导入辅助工具函数
"isolatedModules": true,
// 将每个文件做为单独的模块 (与 'ts.transpileModule' 类似).
/* 严格的类型检查选项 */
"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 语句贯穿)
/* 模块解析选项 */
"moduleResolution": "node",
// 选择模块解析策略: 'node' (Node.js) or 'classic' (TypeScript pre-1.6)
"baseUrl": "./",
// 用于解析非相对模块名称的基目录
"paths": {},
// 模块名到基于 baseUrl 的路径映射的列表
"rootDirs": [],
// 根文件夹列表,其组合内容表示项目运行时的结构内容
"typeRoots": [],
// 包含类型声明的文件列表
"types": [],
// 需要包含的类型声明文件名列表
"allowSyntheticDefaultImports": true,
// 允许从没有设置默认导出的模块中默认导入。
/* Source Map Options */
"sourceRoot": "./",
// 指定调试器应该找到 TypeScript 文件而不是源文件的位置
"mapRoot": "./",
// 指定调试器应该找到映射文件而不是生成文件的位置
"inlineSourceMap": true,
// 生成单个 soucemaps 文件,而不是将 sourcemaps 生成不同的文件
"inlineSources": true,
// 将代码与 sourcemaps 生成到一个文件中,要求同时设置了 --inlineSourceMap 或 --sourceMap 属性
/* 其他选项 */
"experimentalDecorators": true,
// 启用装饰器
"emitDecoratorMetadata": true
// 为装饰器提供元数据的支持
}
}
十六、TypeScript 开发辅助工具
1. TypeScript Playground
- 简介:TypeScript 官方提供的在线 TypeScript 运行环境;在线地址
- 除了 TypeScript 官方的 Playground 之外,你还可以选择其他的 Playground,比如 codepen.io、stackblitz 或 jsbin.com 等。
2. TypeScript UML Playground
- 简介:一款在线 TypeScript UML 工具,生成 UML 类图。在线地址
3. JSON TO TS
- 简介:JSON 数据,生成对应的 TypeScript 接口定义。在线地址 其它
- 除了使用 jsontots在线工具之外,对于使用 VSCode IDE 的小伙们还可以安装 JSON to TS 扩展来快速完成 JSON to TS 的转换工作。
4. Schemats
- 简介:利用 Schemats,可以基于(Postgres,MySQL)SQL 数据库中的 schema 自动生成 TypeScript 接口定义。地址
5. TypeScript AST Viewer
- 简介:查看 TypeScript 代码对应的 AST(Abstract Syntax Tree)抽象语法树。在线地址
- 对于了解过 AST 的小伙伴来说,对 astexplorer 这款在线工具应该不会陌生。该工具除了支持 JavaScript 之外,还支持 CSS、JSON、RegExp、GraphQL 和 Markdown 等格式的解析。
6. TypeDoc
- 简介:TypeDoc 用于将 TypeScript 源代码中的注释转换为 HTML 文档或 JSON 模型。它可灵活扩展,并支持多种配置。在线地址
7. TypeScript ESLint
- 简介:使用 TypeScript ESLint 可以规范代码质量,提高开发效率。在线地址
- Typescript 中使用 ESLint