TypeScript 与 JavaScript 的关系不同寻常。TypeScript 提供了 JavaScript 的所有功能,并在其之上增加了一层:TypeScript 的类型系统。
例如,JavaScript 提供了诸如 string 和 number 这样的语言原语,但它不会检查你是否始终如一地分配了这些类型。而 TypeScript 会。
这意味着你现有的、可运行的 JavaScript 代码同样也是 TypeScript 代码。TypeScript 的主要优势在于它能够突出显示代码中意外的行为,从而降低出现 Bug 的概率。
本教程简要概述了 TypeScript,重点介绍其类型系统。
类型推论
TypeScript 了解 JavaScript 语言,并在许多情况下为你生成类型。例如,在创建变量并将其赋值为特定值时,TypeScript 会使用该值作为其类型。
tsTrylethelloWorld = "Hello World";
通过理解 JavaScript 的工作方式,TypeScript 可以构建一个既接受 JavaScript 代码又具备类型的类型系统。这使得你无需添加额外的字符来显式定义类型。这就是为什么在上面的例子中,TypeScript 知道 helloWorld 是一个 string。
你可能在 Visual Studio Code 中编写过 JavaScript,并体验过编辑器自动补全功能。Visual Studio Code 在底层使用 TypeScript 来使 JavaScript 开发变得更加轻松。
定义类型
你可以在 JavaScript 中使用多种设计模式。然而,有些设计模式使得类型很难自动推断(例如使用动态编程的模式)。为了涵盖这些情况,TypeScript 支持对 JavaScript 语言进行扩展,为你提供告知 TypeScript 类型应该是何种形式的位置。
例如,若要创建一个包含 name: string 和 id: number 的推断类型对象,你可以这样写:
tsTryconstuser = {name : "Hayes",id : 0,};
你可以使用 interface(接口)声明显式描述该对象的形状:
tsTryinterfaceUser {name : string;id : number;}
随后,你可以在变量声明后使用 : TypeName 语法,声明一个 JavaScript 对象符合你新定义的 interface 的形状。
tsTryconstuser :User = {name : "Hayes",id : 0,};
如果你提供的对象与你指定的接口不匹配,TypeScript 会向你发出警告。
tsTryinterfaceUser {name : string;id : number;}constuser :User = {Object literal may only specify known properties, and 'username' does not exist in type 'User'.2353Object literal may only specify known properties, and 'username' does not exist in type 'User'.: "Hayes", username id : 0,};
由于 JavaScript 支持类和面向对象编程,TypeScript 也同样支持。你可以将接口声明与类一起使用。
tsTryinterfaceUser {name : string;id : number;}classUserAccount {name : string;id : number;constructor(name : string,id : number) {this.name =name ;this.id =id ;}}constuser :User = newUserAccount ("Murphy", 1);
你可以使用接口来注解函数的参数和返回值。
tsTryfunctiondeleteUser (user :User ) {// ...}functiongetAdminUser ():User {//...}
JavaScript 中已有一组可用的原始类型:boolean、bigint、null、number、string、symbol 和 undefined,你可以在接口中使用它们。TypeScript 在此基础上扩展了一些类型,例如 any(允许任何内容)、unknown(确保使用此类型的人声明其确切类型)、never(此类型不可能发生)以及 void(返回 undefined 或没有返回值的函数)。
你会发现构建类型有两种语法:接口(Interfaces)和类型别名(Types)。你应该优先选择 interface。当需要特定功能时,再使用 type。
组合类型
使用 TypeScript,你可以通过组合简单类型来创建复杂类型。有两种常用的方法:联合(Unions)和泛型(Generics)。
联合类型
通过联合,你可以声明一个类型可以是多种类型之一。例如,你可以将 boolean 类型描述为 true 或 false。
tsTrytypeMyBool = true | false;
注意:如果你将鼠标悬停在上面的 MyBool 上,会看到它被归类为 boolean。这是结构化类型系统的一个特性。下面会详细介绍。
联合类型的一个流行用例是描述允许作为值的 string 或 number 字面量 集合。
tsTrytypeWindowStates = "open" | "closed" | "minimized";typeLockStates = "locked" | "unlocked";typePositiveOddNumbersUnderTen = 1 | 3 | 5 | 7 | 9;
联合也提供了处理不同类型的方法。例如,你可能有一个接收 array 或 string 的函数。
tsTryfunctiongetLength (obj : string | string[]) {returnobj .length ;}
要了解变量的类型,请使用 typeof:
| 类型 | 谓词 |
|---|---|
| string | typeof s === "string" |
| number | typeof n === "number" |
| boolean | typeof b === "boolean" |
| undefined | typeof undefined === "undefined" |
| 函数 | typeof f === "function" |
| 数组 | Array.isArray(a) |
例如,你可以让函数根据传入的是字符串还是数组返回不同的值。
tsTryfunctionwrapInArray (obj : string | string[]) {if (typeofobj === "string") {return [obj ];}returnobj ;}
泛型
泛型为类型提供了变量。一个常见的例子是数组。没有泛型的数组可以包含任何东西。带有泛型的数组可以描述数组中包含的值的类型。
tstype StringArray = Array<string>;type NumberArray = Array<number>;type ObjectWithNameArray = Array<{ name: string }>;
你可以声明使用泛型的自定义类型。
tsTryinterfaceBackpack <Type > {add : (obj :Type ) => void;get : () =>Type ;}// This line is a shortcut to tell TypeScript there is a// constant called `backpack`, and to not worry about where it came from.declare constbackpack :Backpack <string>;// object is a string, because we declared it above as the variable part of Backpack.constobject =backpack .get ();// Since the backpack variable is a string, you can't pass a number to the add function.Argument of type 'number' is not assignable to parameter of type 'string'.2345Argument of type 'number' is not assignable to parameter of type 'string'.backpack .add (23 );
结构化类型系统
TypeScript 的核心原则之一是类型检查关注的是值的形状。这有时被称为“鸭子类型(duck typing)”或“结构化类型(structural typing)”。
在结构化类型系统中,如果两个对象具有相同的形状,它们就被视为同一类型。
tsTryinterfacePoint {x : number;y : number;}functionlogPoint (p :Point ) {console .log (`${p .x }, ${p .y }`);}// logs "12, 26"constpoint = {x : 12,y : 26 };logPoint (point );
point 变量从未被声明为 Point 类型。然而,TypeScript 在类型检查时会比较 point 的形状和 Point 的形状。它们形状相同,因此代码通过检查。
形状匹配仅要求对象的字段子集匹配即可。
tsTryconstpoint3 = {x : 12,y : 26,z : 89 };logPoint (point3 ); // logs "12, 26"constrect = {x : 33,y : 3,width : 30,height : 80 };logPoint (rect ); // logs "33, 3"constcolor = {hex : "#187ABF" };Argument of type '{ hex: string; }' is not assignable to parameter of type 'Point'. Type '{ hex: string; }' is missing the following properties from type 'Point': x, y2345Argument of type '{ hex: string; }' is not assignable to parameter of type 'Point'. Type '{ hex: string; }' is missing the following properties from type 'Point': x, ylogPoint (); color
类和对象符合形状的方式没有区别。
tsTryclassVirtualPoint {x : number;y : number;constructor(x : number,y : number) {this.x =x ;this.y =y ;}}constnewVPoint = newVirtualPoint (13, 56);logPoint (newVPoint ); // logs "13, 56"
如果对象或类具有所有必需的属性,TypeScript 就会认为它们匹配,而不管具体的实现细节如何。
后续步骤
这是对日常 TypeScript 中使用的语法和工具的简要概述。从这里开始,您可以
- 阅读完整手册 从头到尾
- 探索 Playground 示例