什么是 TypeScript TypeScript is JavaScript with syntax for types. TypeScript is a strongly typed programming language which builds on JavaScript giving you better tooling at any scale.
@typescriptlang 官网
TypeScript 是 JavaScript 的超集
TypeScript 是具有类型语法的 JavaScript。
TypeScript 是一种建立在 JavaScript 之上的强类型编程语言,可为您提供任何规模的更好工具。
Typescript 提供什么类型能力? 在开发过程中,配合 IDE,TS 能实时检查类型的合法性,还能基于类型给出更精准的自动补全等特征提升码字效率。 在编译过程中,TS 仍然能做类型检查,再把 TS 代码编译成 JS 代码。 #### TypeScript 与 JavaScript 的区别
TypeScript JavaScript 具有类型语法的 JavaScript,用于解决大型项目代码复杂性 一种脚本语言,用于创建动态网页 可以在编译期间发现并纠正错误 作为一种解释性语言,只能在运行时发现错误 强类型,支持静态和动态类型 弱类型,没有静态类型选项 最终被编译成 JavaScript 代码,使浏览器可以理解 可以直接在浏览器中使用 支持模块、接口和泛型 有限支持模块、不支持泛型和接口 支持 ES3、ES3、ES5 及 ES2015+、 ESNext 不支持编译 ES3、ES3、ES5 及 ESNext
基础类型 原始类型 1 2 3 4 5 6 7 8 9 10 11 12 13 14 const isDone: boolean = false ;const cout: number = 666 ;const name: string = 'Godfery' ;const theBiggestInt: BigInt = 9007199254740991n ;const privateKey: Symbol = Symbol ('_key' );
当使用 const
、let
、var
声明变量时,可以选择添加「类型注解」指定变量的类型。
数组/元祖 1 2 3 4 5 6 const Fruits: string [] = ['apple' , 'banana' ];const UserNames: Array <string > = ['Tom' , 'Jerry' ];const entry: [string , number ] = ['Tom' , 27 ];
Any 在 TypeScript 中,任何类型都可以被归为 any
类型。当你不想写类型声明的时候可以使用它。
1 2 3 4 5 6 7 8 let obj: any = { x : 0 };obj.foo(); obj(); obj.bar = 100 ; obj = 'hello' ; const n: number = obj;
noImplicitAny 当你不指定类型,并且 TypeScript 不能从上下文推断出类型,编译器通常将默认为 any
。你通常希望避免这种情况,因为any
不会类型检查。使用noImplicitAny
可以配置隐式any
类型抛出异常。
1 2 3 4 5 function fn (s ) { console .log(s.subtr(3 )); } fn(42 );
当 strict 时,默认为 true
,否则默认为 false
null
和 undefined
JavaScript 有两个原始值用于标明不存在或未初始化的值:null
和undefined
。TypeScript 也有两个对应同名类型:null
和undefined
。这些类型的行为如何取决于您是否具有 strictNullChecks 选项。
strictNullChecks
on (默认)当一个值为null
或undefined
,你需要在使用属性或方法前检查这些值。
1 2 3 4 5 6 7 function doSomething (x: string | null ) { if (x === null ) { } else { console .log('Hello, ' + x.toUpperCase()); } }
strictNullChecks
off值为null
或undefined
仍然可以正常的访问。且可以将 null
和undefined
赋值给任意类型。
非空断言运算符(!
后缀) 1 2 3 4 function liveDangerously (x?: number | null ) { console .log(x!.toFixed()); }
注意:就像其他类型断言一样,这并不会改变代码运时的行为。所以请只在你确定值不会是null
或undefined
的时候使用!
对象类型 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 function printCoord (pt: { x: number ; y: number ; z } ) { console .log('坐标的 X 值是' + pt.x); console .log('坐标的 Y 值是' + pt.y); } function printName (obj: { first: string ; last?: string } ) { console .log(obj.last.toUpperCase()); if (obj.last !== undefined ) { console .log(obj.last.toUpperCase()); } console .log(obj.last?.toUpperCase()); } printName({ first : 'Bob' }); printName({ first : 'Alice' , last : 'Alisson' });
你可以用使用;
或者,
来分割属性,并且最后一个分隔符通常是可一省略的。
readonly
属性TypeScript 中可以标记属性为 readonly
。
1 2 3 4 5 6 7 8 9 10 11 12 interface SomeType { readonly prop: string ; } function doSomething (obj: SomeType ) { console .log(`prop has the value '${obj.prop} '.` ); obj.prop = 'hello' ; }
索引签名 有些时候你一开始并不知道所有类型属性的names
,但是你知道 values
的类型。这时你可以使用索引签名来描述可能的值的类型。例如:
1 2 3 4 5 interface StringArray { [index: number ]: string ; } const myArray: StringArray = ['hello' , 'world' ];const secondItem = myArray[1 ];
函数 TypeScript JavaScript 有函数类型 无函数类型 含有类型检查 无类型 箭头函数 箭头函数(ES2015+) 必填和可选参数 所有参数都是可选的 默认参数 默认参数 剩余参数 剩余参数 有函数重载 无函数重载
在 JavaScript 中,函数是头等( first-class) 对象,因为它们可以像任何其他对象 一样具有属性和方法。它们与其他对象的区别在于函数可以被调用。简而言之,它们是Function
对象。
MDN 关于函数的定义
参数类型注解和返回类型注解 1 2 3 4 5 6 7 8 9 function greet (name: string ) { console .log('Hello, ' + name.toUpperCase() + '!!' ); } function getFavoriteNumber ( ): number { return 26 ; }
示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 type Sum1 = (a: number , b: number ) => number ;interface Sum2 { (a: number , b : number ): number ; } type Sum3 = { (a: number , b : number ): number ; }; function sum (a: number , b: number ): number { return a + b; } const sum2: Sum2 = (a, b ) => a + b;const sum3: (a: number , b: number ) => number ;
可选参数及默认参数 在声明函数时,可以通过 ?
号来定义可选参数,比如 age?: number
这种形式。在实际使用时,需要注意的可选参数必须放在普通参数的后面 。
1 2 3 4 5 6 7 8 9 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; }
剩余参数 1 2 3 4 5 function multiply (n: number , ...m: number [] ) { return m.map((x ) => n * x); } const a = multiply(10 , 1 , 2 , 3 , 4 );
#####函数重载
函数重载或方法重载是使用相同名称和不同参数数量或类型创建多个方法的一种能力。
1 2 3 4 5 6 7 8 9 10 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: string | number , b: string | number ) { if (typeof a === 'string' || typeof b === 'string' ) { return a.toString() + b.toString(); } return a + b; }
void 和 undefined void 表示不返回值的函数的返回值。
1 2 3 4 function noop ( ) { return ; }
在 JavaScript 中,一个放回没有返回任何值时,他的返回值就是 undefined。
需要注意的是,声明一个 void 类型的变量没有什么作用,因为它的值只能为 undefined
或 null
:
never never
类型表示的是那些永不存在的值的类型。
1 2 3 4 5 6 7 8 function error (message: string ): never { throw new Error (message); } function infiniteLoop ( ): never { while (true ) {} }
类型声明 interface 和 type interface
type
type 和 interface 的区别 type 可以而 interface 不行 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 type Name = string ;interface Dog { wong(); } interface Cat { miao(); } type Pet = Dog | Cat;type PetList = [Dog, Pet];
type 语句中还可以使用 typeof 获取实例的 类型进行赋值
1 2 3 let div = document .createElement('div' );type B = typeof div;
其他骚操作
1 2 3 4 5 6 7 type StringOrNumber = string | number ;type Text = string | { text : string };type NameLookup = Dictionary<string , Person>;type Callback<T> = (data: T ) => void ;type Pair<T> = [T, T];type Coordinates = Pair<number >;type Tree<T> = T | { left : Tree<T>; right: Tree<T> };
interface 可以而 type 不行 类型合并
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 interface User { name: string ; age: number ; } interface User { sex: string ; }
class 类的属性和方法 在面向对象语言中,类是一种面向对象计算机编程语言的构造,是创建对象的蓝图,描述了所创建的对象共同的属性和方法。 在 TypeScript 中,我们可以通过 Class 关键字来定义一个类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 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 代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 'use strict' ;var Greeter = (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' );
访问器 我们可以通过 getter
和 setter
方法来实现数据的封装和有效性校验,防止出现异常数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 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); }
类的继承 继承 (Inheritance) 是一种联结类与类的层次模型。 指的是一个类(称为子类、子接口)继承另外的一个类(称为父类、父接口)的功能,并可以增加它自己的新功能的能力,继承是类与类或者接口与接口之间最常见的关系。
继承是一种 is-a 关系:
在 TypeScript 中,可以通过 extends 关键字来实现继承:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 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();
enum 使用枚举我们可以定义一些带名字的常量。 TypeScript 支持数字的和基于字符串的枚举。
数字枚举值 1 2 3 4 5 6 7 8 enum Direction { NORTH, SOUTH, EAST, WEST, } let dir: Direction = Direction.NORTH;
看一下编译结果
1 2 3 4 5 6 7 8 'use strict' ;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 = {}));
默认情况下,NORTH 的初始值为 0,其余的成员会从 1 开始自动增长,也可以手动设置枚举值的初始值。enum 会为数字类型的枚举设置“反向映射”,也就是为对应的值,设置他们的 key。所以数字类型的枚举值,可以用其对应的值,取到 key。
字符串枚举值 1 2 3 4 5 6 enum Direction { NORTH = 'NORTH' , SOUTH = 'SOUTH' , EAST = 'EAST' , WEST = 'WEST' , }
看一下编译代码
1 2 3 4 5 6 7 8 'use strict' ;var Direction;(function (Direction ) { Direction['NORTH' ] = 'NORTH' ; Direction['SOUTH' ] = 'SOUTH' ; Direction['EAST' ] = 'EAST' ; Direction['WEST' ] = 'WEST' ; })(Direction || (Direction = {}));
如果使用字符串枚举值,则需要为每个枚举赋值,否则其值为 undefined
异构枚举 异构枚举的成员值是数字和字符串的混合:
1 2 3 4 5 6 7 8 enum Enum { A, B, C = 'C' , D = 'D' , E = 8 , F, }
类型运算和派生 字面量类型 字符串字面量
1 type Fruits = 'apple' | 'banana' | 'orange' ;
其他字面量类型
1 2 3 type OneToFive = 1 | 2 | 3 | 4 | 5 ;type Bools = true | false ;
模板字面量类型 模板字面量类型建立在字符串文字类型之上,并且能够通过联合扩展成许多字符串。
1 2 3 type EmailLocaleIDs = 'welcome_email' | 'email_heading' ;type FooterLocaleIDs = 'footer_title' | 'footer_sendoff' ;type AllLocaleIDs = `${EmailLocaleIDs | FooterLocaleIDs} _id` ;
内部字符串操作类型 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 type ASCIICacheKey<Str extends string > = `ID-${Uppercase<Str>} ` ;type MainID = ASCIICacheKey<'my_app' >;type ASCIICacheKey<Str extends string > = `id-${Lowercase<Str>} ` ;type MainID = ASCIICacheKey<'MY_APP' >;type LowercaseGreeting = 'hello, world' ;type Greeting = Capitalize<LowercaseGreeting>;type UppercaseGreeting = 'HELLO WORLD' ;type UncomfortableGreeting = Uncapitalize<UppercaseGreeting>;
联合类型 1 2 3 4 5 6 7 8 type A = { a: string ; }; type B = { b: string ; }; type Union = A | B | null ;
联合类型和守卫 TypeScript 可辨识联合(Discriminated Unions)类型,也称为代数数据类型或标签联合类型。它包含 3 个要点:可辨识、联合类型和类型守卫。
可辨识 可辨识要求联合类型中的每个元素都含有一个单例类型属性,比如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 enum CarTransmission { Automatic = 200 , Manual = 300 , } interface Motorcycle { type : 'motorcycle' ; make: number ; } interface Car { type : 'car' ; transmission: CarTransmission; } interface Truck { type : 'truck' ; capacity: number ; }
我们分别定义了 Motorcycle
、 Motorcycle
和 Truck
三个接口,在这些接口中都包含一个 type
属性,该属性被称为可辨识的属性,而其它的属性只跟特性的接口相关。
联合类型 基于前面定义了三个接口,我们可以创建一个 Vehicle
联合类型: 现在我们就可以开始使用 Vehicle
联合类型,对于 Vehicle
类型的变量,它可以表示不同类型的车辆。
1 type Vehicle = Motorcycle | Car | Truck;
类型守卫 1 2 3 4 5 6 7 8 9 10 11 12 13 function evaluatePrice (vehicle: Vehicle ) { switch (vehicle.type) { case 'car' : return vehicle.transmission * EVALUATION_FACTOR; case 'truck' : return vehicle.capacity * EVALUATION_FACTOR; case 'motorcycle' : return vehicle.make * EVALUATION_FACTOR; } } const myTruck: Truck = { vType : 'truck' , capacity : 9.5 };evaluatePrice(myTruck);
在以上代码中,我们使用 switch
和 case
运算符来实现类型守卫,从而确保在 evaluatePrice
方法中,我们可以安全地访问 vehicle
对象中的所包含的属性,来正确的计算该车辆类型所对应的价格。
交叉类型 1 2 3 4 5 6 7 8 9 10 interface IPerson { id: string ; age: number ; } interface IWorker { companyId: string ; } type IStaff = IPerson & IWorker;
泛型 泛型接口 1 2 3 interface GenericIdentityFn<T> { (arg: T): T; }
泛型类 1 2 3 4 5 6 7 8 9 10 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; };
操作符 typeof 在 TypeScript 中,typeof
操作符可以用来获取一个变量声明或对象的类型。
1 2 3 4 5 function toArray (x: number ): number [] { return [x]; } type Func = typeof toArray;
keyof keyof
操作符可以用来一个对象中的所有 key
值:
1 2 3 4 5 6 interface Person { name: string ; age: number ; } type Keys = keyof Person;
in in
用来遍历枚举类型
1 2 3 4 5 type Keys = 'a' | 'b' | 'c' ;type Obj = { [p in Keys]: any ; };
extends 有时候我们定义的泛型不想过于灵活或者说想继承某些类等,可以通过 extends 关键字添加泛型约束。
1 2 3 4 5 6 7 8 interface ILengthwise { length: number ; } function loggingIdentity <T extends ILengthwise >(arg: T ): T { console .log(arg.length); return arg; }
索引访问类型 我们可以使用索引访问类型查找另一个类型的指定属性。
1 2 type Person = { age : number ; name: string ; alive: boolean };type Age = Person['age' ];
条件类型 我们可以借助 extends
关键字实现类似 if
的操作:
1 2 3 4 5 6 7 8 9 10 11 12 interface Animal { live(): void ; } interface Dog extends Animal { woof(): void ; } type Example1 = Dog extends Animal ? number : string ;type Example1 = number ;type Example2 = RegExp extends Animal ? number : string ;
来个有意思的小例子
1 2 3 4 5 6 7 8 9 10 11 12 13 interface IdLabel { id: number ; } interface NameLabel { name: string ; } function createLabel (id: number ): IdLabel ;function createLabel (name: string ): NameLabel ;function createLabel (nameOrId: string | number ): IdLabel | NameLabel ;function createLabel (nameOrId: string | number ): IdLabel | NameLabel { throw 'unimplemented' ; }
然后,我们可以使用该条件类型将重载简化为单个函数,而不需要重载。
1 2 3 4 5 6 7 8 9 type NameOrId<T extends number | string > = T extends number ? IdLabel : NameLabel;function createLabel <T extends number | string >(idOrName: T ): NameOrId <T > { throw 'unimplemented' ; } let a = createLabel('typescript' );let b = createLabel(2.8 );let c = createLabel(Math .random() ? 'hello' : 42 );
另一个例子,我们也可以写一个叫 Flatten 的类型,它把数组类型平坦化为元素类型,但是不使用其他类型:
1 2 3 type Flatten<T> = T extends any [] ? T[number ] : T;type Str = Flatten<string []>;type Num = Flatten<number >;
infer 条件类型为我们提供了一种使用 infer
关键字从真实分支中比较的类型推断出结果的方法。
1 type Flatten<Type> = Type extends Array <infer Item> ? Item : Type;
我们可以利用 infer
关键字编写一些有用的 helper 类型别名。
1 2 3 4 5 type GetReturnType<Type> = Type extends (...args: never []) => infer Return ? Return : never ;type Num = GetReturnType<() => number >;type Str = GetReturnType<(x: string ) => string >;type Bools = GetReturnType<(a: boolean , b: boolean ) => boolean []>;
映射类型 映射类型建立在索引签名的语法之上,索引签名用于声明未提前声明的属性类型
1 2 3 type OnlyBoolsAndHorses = { [key: string ]: boolean | Horse; };
映射类型是一种泛型类型,它使用 PropertyKeys (通常通过 keyof 创建)的联合来迭代键以创建类型:
1 2 3 type OptionsFlags<Type> = { [Property in keyof Type]: boolean ; };
在本例中,OptionsFlags 将获取 Type 类型中的所有属性,并将其值更改为布尔值
1 2 3 4 5 6 type FeatureFlags = { darkMode: () => void ; newUserProfile: () => void ; }; type FeatureOptions = OptionsFlags<FeatureFlags>;
映射修饰符 在映射过程中可以使用两个额外的修饰符: readonly
和 ?
,它们分别影响可变性和可选性。 您可以通过使用 -
或 +
作为前缀来删除或添加这些修饰符。如果您没有添加前缀,则假定为 +
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 type CreateMutable<Type> = { -readonly [Property in keyof Type]: Type[Property]; }; type LockedAccount = { readonly id: string ; readonly name: string ; }; type UnlockedAccount = CreateMutable<LockedAccount>;type Concrete<Type> = { [Property in keyof Type]-?: Type[Property]; }; type MaybeUser = { id: string ; name?: string ; age?: number ; }; type User = Concrete<MaybeUser>;
在 TypeScript 4.1 及以后的版本中,您可以使用映射类型中的 as 子句重新映射映射类型中的键:
1 2 3 type MappedTypeWithNewProperties<Type> = { [Properties in keyof Type as NewKeyType]: Type[Properties]; };
可以利用一些特性,比如模板字面量类型,从以前的属性中创建新的属性名:
1 2 3 4 5 6 7 8 9 10 11 type Getters<Type> = { [Property in keyof Type as `get${Capitalize<string & Property>} ` ]: () => Type[Property]; }; interface Person { name: string ; age: number ; location: string ; } type LazyPerson = Getters<Person>;
参考