一、基本类型:stringnumberboolean

JavaScript 有三个常用的基本类型:stringnumberboolean,TypeScript 与之对应

  • string:例如 "Hello World"
  • number:例如42
  • booleantruefalse

小知识

  • StringNumberBoolean(首字母大写)也是合法的,但是像这种内置类型在我们代码中很少见
  • 我们始终使用首字母小写的stringnumberboolean

二、Array

[1,2,3]可以使用语法number[],字符串数组可以使用string[],后面遇到范型后我们再学习T<U>语法

小知识

  • [number]不太一样,需要看元祖类型章节

三、any

TypeScript 有一个特殊的类型any,只要你不希望某个特定值导致类型检查错误,就可以使用它。

当值为 any 类型时,您可以访问它的任何属性(反过来又是 any 类型),像函数一样调用它,将其分配给(或来自)任何类型的值,或者语法上合法的几乎任何其他内容:

  1. let obj: any = { x: 0 };
  2. // None of the following lines of code will throw compiler errors.
  3. // Using `any` disables all further type checking, and it is assumed
  4. // you know the environment better than TypeScript.
  5. obj.foo();
  6. obj();
  7. obj.bar = 100;
  8. obj = "hello";
  9. const n: number = obj;

noImplicitAny

我们可以通过设置noImplicitAny:true让变量为 any的变量是一个错误,强迫我们写类型

五、变量上的类型注解

当我们使用constvarlet声明变量时,可以显式地给变量添加类型注解

  1. let myName: string = "Alice";

通常,上面这种添加类型注解是不必要,TypeScript 能够根据代码自动推断类型

六、函数

函数是 JavaScript 中传递数据的主要方式,TypeScript 允许指定函数的输入值和输出值的类型

参数类型注解

声明函数时候,可以在每个参数后添加类型注解

  1. // Parameter type annotation
  2. function greet(name: string) {
  3. console.log("Hello, " + name.toUpperCase() + "!!");
  4. }

小知识

  • 如果没有在参数上写注解,typescript 会检查参数的个数是否一致

返回值类型注解

  1. function getFavoriteNumber(): number {
  2. return 26;
  3. }
  • 与变量类型注解非常相似,通常不需要返回值类型注解,因为 typescript 会根据其 return语句推断函数的返回类型
  • 某些代码库将显式指定返回类型,用于文档目的,以防止异味篡改,或仅用于个人首选项

匿名函数

当一个函数出现在TypeScript可以确定如何调用它的地方时,该函数的参数会自动被赋予类型。下面是个示例

  1. // No type annotations here, but TypeScript can spot the bug
  2. const names = ["Alice", "Bob", "Eve"];
  3. // Contextual typing for function
  4. names.forEach(function (s) {
  5. console.log(s.toUppercase());
  6. // Property 'toUppercase' does not exist on type 'string'. Did you mean 'toUpperCase'?
  7. });
  8. // Contextual typing also applies to arrow functions
  9. names.forEach((s) => {
  10. console.log(s.toUppercase());
  11. // Property 'toUppercase' does not exist on type 'string'. Did you mean 'toUpperCase'?
  12. });

即使参数 s 没有类型注释,TypeScript 也使用 forEach 函数的类型以及数组的推断类型来确定 s 将具有的类型

七、对象类型

除了基本类型之外,您将遇到的最常见的类型类型是对象类型。这是指任何带有属性的JavaScript值。要定义对象类型,我们只需列出其属性及其类型。

  1. // The parameter's type annotation is an object type
  2. function printCoord(pt: { x: number; y: number }) {
  3. console.log("The coordinate's x value is " + pt.x);
  4. console.log("The coordinate's y value is " + pt.y);
  5. }
  6. printCoord({ x: 3, y: 7 });
  • 在这里,我们使用具有两个属性的类型(x 和 y)对参数进行注解,这两个属性都是number类型。您可以使用 ,;以分隔属性,并且最后一个分隔符是可选的。
  • 每个属性的类型部分也是可选的。如果未指定类型,则假定为任意类型。

可选属性

对象类型还可以指定其部分或全部属性是可选的。为此,请添加?在属性名称之后:

  1. function printName(obj: { first: string; last?: string }) {
  2. // ...
  3. }
  4. // Both OK
  5. printName({ first: "Bob" });
  6. printName({ first: "Alice", last: "Alisson" });

在 JavaScript 中,如果访问不存在的属性,则将获得undefined值,而不是运行时错误。因此,当您从可选属性读取时,必须在使用它之前检查undefined

  1. function printName(obj: { first: string; last?: string }) {
  2. // Error - might crash if 'obj.last' wasn't provided!
  3. console.log(obj.last.toUpperCase());
  4. // Object is possibly 'undefined'.
  5. if (obj.last !== undefined) {
  6. // OK
  7. console.log(obj.last.toUpperCase());
  8. }
  9. // A safe alternative using modern JavaScript syntax:

八、union 类型

TypeScript 的类型系统允许您使用各种运算符从现有类型中构建新类型。现在我们已经知道如何编写几种类型,现在是时候开始以有趣的方式将它们组合起来了。

定义一个 union 类型

组合类型的第一种方法是union类型。一个union类型是由两个或多个其他类型组成的类型,表示可能是这些类型中的任何一个的值。我们将这些类型中的每一种称为union的成员。

  1. function printId(id: number | string) {
  2. console.log("Your ID is: " + id);
  3. }
  4. // OK
  5. printId(101);
  6. // OK
  7. printId("202");
  8. // Error
  9. printId({ myID: 22342 });
  10. // Argument of type '{ myID: number; }' is not assignable to parameter of type 'string | number'.
  11. // Type '{ myID: number; }' is not assignable to type 'number'.

使用 union 类型

TypeScript 将仅允许对 union 的每个成员有效的操作。例如,如果您有union string | number,您不能使用仅在string上可用的方法:

  1. function printId(id: number | string) {
  2. console.log(id.toUpperCase());
  3. // Property 'toUpperCase' does not exist on type 'string | number'.
  4. // Property 'toUpperCase' does not exist on type 'number'.
  5. }

解决方案是缩小与代码的并集,就像在没有类型注释的JavaScript中一样。当 TypeScript 可以根据代码的结构为值推断出更具体的类型时,就会发生缩小范围。

下面是一个例子,使用typeof判断

  1. function printId(id: number | string) {
  2. if (typeof id === "string") {
  3. // In this branch, id is of type 'string'
  4. console.log(id.toUpperCase());
  5. } else {
  6. // Here, id is of type 'number'
  7. console.log(id);
  8. }
  9. }

还有一个例子,是使用函数,类似Array.isArray

  1. function welcomePeople(x: string[] | string) {
  2. if (Array.isArray(x)) {
  3. // Here: 'x' is 'string[]'
  4. console.log("Hello, " + x.join(" and "));
  5. } else {
  6. // Here: 'x' is 'string'
  7. console.log("Welcome lone traveler " + x);
  8. }
  9. }
  • else分支中,它能推断出 x是一个 string类型

如果联合中的每个成员都有一个共同的属性,则可以使用该属性而不缩小范围,例如,数组和字符串都有一个 slice 方法

  1. // Return type is inferred as number[] | string
  2. function getFirstThree(x: number[] | string) {
  3. return x.slice(0, 3);
  4. }

九、类型别名

我们一直在使用对象类型和联合类型,直接在类型注释中编写它们。这很方便,但通常希望多次使用相同的类型,并用单个名称引用它。

类型别名就是“类型的名字”,语法如下

  1. type Point = {
  2. x: number;
  3. y: number;
  4. };
  5. // Exactly the same as the earlier example
  6. function printCoord(pt: Point) {
  7. console.log("The coordinate's x value is " + pt.x);
  8. console.log("The coordinate's y value is " + pt.y);
  9. }
  10. printCoord({ x: 100, y: 100 });

您实际上可以使用类型别名为任何类型命名,而不仅仅是对象类型。例如,类型别名可以命名联合类型:

  1. type ID = number | string;

请注意,别名只是别名,即您不能使用类型别名来创建相同类型的不同/不同的”版本”。使用别名时,就像您编写了别名类型一样。换句话说,此代码可能看起来是非法的,但根据TypeScript是可以的,因为这两种类型都是同一类型的别名:

  1. type UserInputSanitizedString = string;
  2. function sanitizeInput(str: string): UserInputSanitizedString {
  3. return sanitize(str);
  4. }
  5. // Create a sanitized input
  6. let userInput = sanitizeInput(getInput());
  7. // Can still be re-assigned with a string though
  8. userInput = "new input";

十、接口

接口声明是命名对象类型的另一种方式:

  1. interface Point {
  2. x: number;
  3. y: number;
  4. }
  5. function printCoord(pt: Point) {
  6. console.log("The coordinate's x value is " + pt.x);
  7. console.log("The coordinate's y value is " + pt.y);
  8. }
  9. printCoord({ x: 100, y: 100 });

就像我们在上面使用类型别名时一样,该示例的工作方式就像我们使用匿名对象类型一样。TypeScript只关心我们传递给printCoord的值的结构 - 它只关心它是否具有预期的属性。只关注类型的结构和功能,这就是我们称TypeScript为结构类型化类型系统的原因。

类型别名与接口的区别

类型别名和接口非常相似,在许多情况下,您可以在它们之间自由选择。interface的几乎所有功能在type中都可用,关键区别在于type不能重新打开以添加新属性,而interface始终是可扩展的。
image.png

您将在后面的章节中了解有关这些概念的更多信息,因此,如果您没有立即理解所有这些概念,请不要担心。

  • 在 TypeScript 版本 4.2 之前,类型别名可能会出现在错误消息中,有时会代替等效的匿名类型(可能需要也可能不需要)。接口将始终在错误消息中命名。
  • 类型别名可能不参与声明合并,但接口可以。
  • 接口只能用于声明对象的形状,而不能重命名基本类型。
  • 接口名称将始终以其原始形式显示在错误消息中,但仅当它们按名称使用时。

在大多数情况下,您可以根据个人喜好进行选择,TypeScript会告诉您是否需要另一种类型的声明。如果您想要启发式,请使用interface,直到您需要使用type中的功能

十一、类型断言

有时,您将获得有关 TypeScript 无法知道的值类型的信息。

例如,如果您使用的是 document.getElementById,TypeScript 只知道这将返回某种 HTMLElement,但您可能知道您的页面将始终具有具有给定 ID 的 HTMLCanvasElement

在此情况下,可以使用类型断言来指定更具体的类型:

  1. const myCanvas = document.getElementById("main_canvas") as HTMLCanvasElement;

与类型批注一样,编译器会删除类型断言,并且不会影响代码的运行时行为。

您还可以使用尖括号语法(除非代码位于 .tsx 文件中),该语法等效:

  1. const myCanvas = <HTMLCanvasElement>document.getElementById("main_canvas");
  • 提醒:由于类型断言在编译时被删除,因此没有与类型断言关联的运行时检查。如果类型断言错误,则不会生成异常或 null。

TypeScript 仅允许转换为更具体或更不具体的类型的类型断言。此规则可防止”不可能”的强制,例如:

  1. const x = "hello" as number;
  2. // Conversion of type 'string' to type 'number' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.

有时,此规则可能过于保守,并且不允许可能有效的更复杂的强制。如果发生这种情况,您可以使用两个断言,首先是any断言(或unknown,我们将在后面介绍),然后是所需的类型:

  1. const a = (expr as any) as T;

十二、字面量类型

除了一般的类型stringnumber之外,我们还可以引用类型位置中的特定字符串和数字。

考虑这个问题的一种方法是考虑JavaScript如何以不同的方式声明变量。varlet都允许更改变量内部保存的内容,而const则不允许。这反映在 TypeScript 如何为文本创建类型中。

  1. let changingString = "Hello World";
  2. changingString = "Olá Mundo";
  3. // Because `changingString` can represent any possible string, that
  4. // is how TypeScript describes it in the type system
  5. changingString;
  6. // let changingString: string
  7. const constantString = "Hello World";
  8. // Because `constantString` can only represent 1 possible string, it
  9. // has a literal type representation
  10. constantString;
  11. // const constantString: "Hello World"

就其本身而言,文字类型并不是很有价值:

  1. let x: "hello" = "hello";
  2. // OK
  3. x = "hello";
  4. // ...
  5. x = "howdy";
  6. // Type '"howdy"' is not assignable to type '"hello"'.

拥有一个只能有一个值的变量没有多大用处!
但是,通过组合字面量类型到 union,您可以表达一个更有用的概念 - 例如,仅接受一组特定已知值的函数:

  1. function printText(s: string, alignment: "left" | "right" | "center") {
  2. // ...
  3. }
  4. printText("Hello, world", "left");
  5. printText("G'day, mate", "centre");
  6. // Argument of type '"centre"' is not assignable to parameter of type '"left" | "right" | "center"'.

数字字面量类型的使用原理类似

  1. function compare(a: string, b: string): -1 | 0 | 1 {
  2. return a === b ? 0 : a > b ? 1 : -1;
  3. }

当然,也能组合非字面量类型

  1. interface Options {
  2. width: number;
  3. }
  4. function configure(x: Options | "auto") {
  5. // ...
  6. }
  7. configure({ width: 100 });
  8. configure("auto");
  9. configure("automatic");
  10. // Argument of type '"automatic"' is not assignable to parameter of type 'Options | "auto"'.

还有一种字面量类型:布尔字面量。只有两种布尔文本类型,正如您可能猜到的那样,它们是truefalse类型。boolean类型本身实际上就是联合类型true | false

字面量推断

使用对象初始化变量时,TypeScript 假定该对象的属性以后可能会更改值。例如,如果您编写了如下代码:

  1. const obj = { counter: 0 };
  2. if (someCondition) {
  3. obj.counter = 1;
  4. }

TypeScript 不假定将 1 分配给以前具有 0 的字段是一个错误。另一种说法是,obj.counter 必须具有类型编号,而不是 0,因为类型用于确定读取和写入行为。
这同样适用于字符串:

  1. const req = { url: "https://example.com", method: "GET" };
  2. handleRequest(req.url, req.method);
  3. // Argument of type 'string' is not assignable to parameter of type '"GET" | "POST"'.

在上面的示例中,req.method被推断为字符串,而不是"GET"。因为代码可以在创建 req 和调用 handleRequest 之间进行计算,后者可以将一个新字符串(如"GUESS")分配给 req.method,所以 TypeScript 认为此代码有错误。

有两种方法可以解决此问题。

  1. 您可以通过在任一位置添加类型断言来更改推理:
    1. // Change 1:
    2. const req = { url: "https://example.com", method: "GET" as "GET" };
    3. // Change 2
    4. handleRequest(req.url, req.method as "GET");
  • 更改 1 表示”我打算让 req.method 始终具有文字类型"GET",从而防止在之后可能将"GUESS"分配给该字段。更改 2 表示”由于其他原因,我知道 req.method 的值为 "GET"
  1. 您可以使用 const 将整个对象转换为类型文本:
    1. const req = { url: "https://example.com", method: "GET" } as const;
    2. handleRequest(req.url, req.method);
  • as const后缀的作用类似于 const,但对于类型系统,确保为所有属性分配文本类型,而不是更常规的版本(如stringnumber)。

十三、nullundefined

JavaScript 有两个基本类型值,用于表示不存在或未初始化的值:nullundefined

TypeScript 具有两个同名的相应类型。这些类型的行为方式取决于您是否启用了strictNullChecks选项。

strictNullChecks off

关闭 strictNullChecks 后,仍可正常访问可能为 null 或未定义的值,并且可以将 null 和 undefined 值分配给任何类型的属性。这类似于没有空检查的语言(例如.C#,Java)的行为方式。缺乏对这些值的检查往往是错误的主要来源;我们总是建议人们打开strictNullChecks,如果在他们的代码库中这样做是可行的。

strictNullChecks on

启用 strictNullChecks 后,当值为 null 或未定义时,您需要先测试这些值,然后再对该值使用方法或属性。就像在使用可选属性之前检查未定义一样,我们可以使用缩小范围来检查可能为 null 的值:

  1. function doSomething(x: string | null) {
  2. if (x === null) {
  3. // do nothing
  4. } else {
  5. console.log("Hello, " + x.toUpperCase());
  6. }
  7. }

非空断言运算符(后缀!)

TypeScript 还具有一种特殊语法,用于从类型中删除 null 和 undefined,而无需执行任何显式检查。写!在任何表达式之后表示一个类型断言值不为nullundefined

  1. function liveDangerously(x?: number | null) {
  2. // No error
  3. console.log(x!.toFixed());
  4. }

就像其他类型断言一样,这不会更改代码的运行时行为,因此仅使用!当您知道该值不能为空或未定义时。

十四、枚举

枚举是由TypeScript添加到JavaScript中的一项功能,它允许描述一个值,该值可能是一组可能的命名常量之一。与大多数TypeScript功能不同,这不是对JavaScript的类型级添加,而是添加到语言和运行时中的东西。因此,这是您应该知道存在的功能,但除非您确定,否则可能会推迟使用。可以在枚举参考页中阅读有关枚举的详细信息。

十五、不太常见的基本类型

值得一提的是JavaScript中在类型系统中表示的其余基本类型。虽然我们不会在这里深入探讨。

bigint

从ES2020开始,JavaScript中有一个用于非常大的整数的原语,BigInt:

  1. // Creating a bigint via the BigInt function
  2. const oneHundred: bigint = BigInt(100);
  3. // Creating a BigInt via the literal syntax
  4. const anotherHundred: bigint = 100n;

您可以在 TypeScript 3.2 发行说明 中了解有关 BigInt 的更多信息。

symbol

JavaScript 中有一个基本类型,用于通过函数 Symbol() 创建全局唯一引用:

  1. const firstName = Symbol("name");
  2. const secondName = Symbol("name");
  3. if (firstName === secondName) {
  4. // This condition will always return 'false' since the types 'typeof firstName' and 'typeof secondName' have no overlap.
  5. // Can't ever happen
  6. }

您可以在 Symbols 参考页面中了解有关它们的更多信息。

十六、参考链接

常用类型:https://www.typescriptlang.org/docs/handbook/2/everyday-types.html