一、基本类型:string
、number
、boolean
JavaScript 有三个常用的基本类型:string
、number
、boolean
,TypeScript 与之对应
string
:例如"Hello World"
number
:例如42
boolean
:true
和false
小知识
String
、Number
、Boolean
(首字母大写)也是合法的,但是像这种内置类型在我们代码中很少见- 我们始终使用首字母小写的
string
、number
、boolean
二、Array
[1,2,3]
可以使用语法number[]
,字符串数组可以使用string[]
,后面遇到范型后我们再学习T<U>
语法
小知识
[number]
不太一样,需要看元祖类型章节
三、any
TypeScript 有一个特殊的类型any
,只要你不希望某个特定值导致类型检查错误,就可以使用它。
当值为 any 类型时,您可以访问它的任何属性(反过来又是 any 类型),像函数一样调用它,将其分配给(或来自)任何类型的值,或者语法上合法的几乎任何其他内容:
let obj: any = { x: 0 };
// None of the following lines of code will throw compiler errors.
// Using `any` disables all further type checking, and it is assumed
// you know the environment better than TypeScript.
obj.foo();
obj();
obj.bar = 100;
obj = "hello";
const n: number = obj;
noImplicitAny
我们可以通过设置noImplicitAny:true
让变量为 any
的变量是一个错误,强迫我们写类型
五、变量上的类型注解
当我们使用const
、var
、let
声明变量时,可以显式地给变量添加类型注解
let myName: string = "Alice";
通常,上面这种添加类型注解是不必要,TypeScript 能够根据代码自动推断类型
六、函数
函数是 JavaScript 中传递数据的主要方式,TypeScript 允许指定函数的输入值和输出值的类型
参数类型注解
声明函数时候,可以在每个参数后添加类型注解
// Parameter type annotation
function greet(name: string) {
console.log("Hello, " + name.toUpperCase() + "!!");
}
小知识
- 如果没有在参数上写注解,typescript 会检查参数的个数是否一致
返回值类型注解
function getFavoriteNumber(): number {
return 26;
}
- 与变量类型注解非常相似,通常不需要返回值类型注解,因为 typescript 会根据其
return
语句推断函数的返回类型 - 某些代码库将显式指定返回类型,用于文档目的,以防止异味篡改,或仅用于个人首选项
匿名函数
当一个函数出现在TypeScript可以确定如何调用它的地方时,该函数的参数会自动被赋予类型。下面是个示例
// No type annotations here, but TypeScript can spot the bug
const names = ["Alice", "Bob", "Eve"];
// Contextual typing for function
names.forEach(function (s) {
console.log(s.toUppercase());
// Property 'toUppercase' does not exist on type 'string'. Did you mean 'toUpperCase'?
});
// Contextual typing also applies to arrow functions
names.forEach((s) => {
console.log(s.toUppercase());
// Property 'toUppercase' does not exist on type 'string'. Did you mean 'toUpperCase'?
});
即使参数 s 没有类型注释,TypeScript 也使用 forEach 函数的类型以及数组的推断类型来确定 s 将具有的类型
七、对象类型
除了基本类型之外,您将遇到的最常见的类型类型是对象类型。这是指任何带有属性的JavaScript值。要定义对象类型,我们只需列出其属性及其类型。
// The parameter's type annotation is an object type
function printCoord(pt: { x: number; y: number }) {
console.log("The coordinate's x value is " + pt.x);
console.log("The coordinate's y value is " + pt.y);
}
printCoord({ x: 3, y: 7 });
- 在这里,我们使用具有两个属性的类型(x 和 y)对参数进行注解,这两个属性都是
number
类型。您可以使用,
或;
以分隔属性,并且最后一个分隔符是可选的。 - 每个属性的类型部分也是可选的。如果未指定类型,则假定为任意类型。
可选属性
对象类型还可以指定其部分或全部属性是可选的。为此,请添加?
在属性名称之后:
function printName(obj: { first: string; last?: string }) {
// ...
}
// Both OK
printName({ first: "Bob" });
printName({ first: "Alice", last: "Alisson" });
在 JavaScript 中,如果访问不存在的属性,则将获得undefined
值,而不是运行时错误。因此,当您从可选属性读取时,必须在使用它之前检查undefined
。
function printName(obj: { first: string; last?: string }) {
// Error - might crash if 'obj.last' wasn't provided!
console.log(obj.last.toUpperCase());
// Object is possibly 'undefined'.
if (obj.last !== undefined) {
// OK
console.log(obj.last.toUpperCase());
}
// A safe alternative using modern JavaScript syntax:
八、union 类型
TypeScript 的类型系统允许您使用各种运算符从现有类型中构建新类型。现在我们已经知道如何编写几种类型,现在是时候开始以有趣的方式将它们组合起来了。
定义一个 union 类型
组合类型的第一种方法是union
类型。一个union
类型是由两个或多个其他类型组成的类型,表示可能是这些类型中的任何一个的值。我们将这些类型中的每一种称为union
的成员。
function printId(id: number | string) {
console.log("Your ID is: " + id);
}
// OK
printId(101);
// OK
printId("202");
// Error
printId({ myID: 22342 });
// Argument of type '{ myID: number; }' is not assignable to parameter of type 'string | number'.
// Type '{ myID: number; }' is not assignable to type 'number'.
使用 union 类型
TypeScript 将仅允许对 union 的每个成员有效的操作。例如,如果您有union string | number
,您不能使用仅在string
上可用的方法:
function printId(id: number | string) {
console.log(id.toUpperCase());
// Property 'toUpperCase' does not exist on type 'string | number'.
// Property 'toUpperCase' does not exist on type 'number'.
}
解决方案是缩小与代码的并集,就像在没有类型注释的JavaScript中一样。当 TypeScript 可以根据代码的结构为值推断出更具体的类型时,就会发生缩小范围。
下面是一个例子,使用typeof
判断
function printId(id: number | string) {
if (typeof id === "string") {
// In this branch, id is of type 'string'
console.log(id.toUpperCase());
} else {
// Here, id is of type 'number'
console.log(id);
}
}
还有一个例子,是使用函数,类似Array.isArray
function welcomePeople(x: string[] | string) {
if (Array.isArray(x)) {
// Here: 'x' is 'string[]'
console.log("Hello, " + x.join(" and "));
} else {
// Here: 'x' is 'string'
console.log("Welcome lone traveler " + x);
}
}
- 在
else
分支中,它能推断出x
是一个string
类型
如果联合中的每个成员都有一个共同的属性,则可以使用该属性而不缩小范围,例如,数组和字符串都有一个 slice 方法
// Return type is inferred as number[] | string
function getFirstThree(x: number[] | string) {
return x.slice(0, 3);
}
九、类型别名
我们一直在使用对象类型和联合类型,直接在类型注释中编写它们。这很方便,但通常希望多次使用相同的类型,并用单个名称引用它。
类型别名就是“类型的名字”,语法如下
type Point = {
x: number;
y: number;
};
// Exactly the same as the earlier example
function printCoord(pt: Point) {
console.log("The coordinate's x value is " + pt.x);
console.log("The coordinate's y value is " + pt.y);
}
printCoord({ x: 100, y: 100 });
您实际上可以使用类型别名为任何类型命名,而不仅仅是对象类型。例如,类型别名可以命名联合类型:
type ID = number | string;
请注意,别名只是别名,即您不能使用类型别名来创建相同类型的不同/不同的”版本”。使用别名时,就像您编写了别名类型一样。换句话说,此代码可能看起来是非法的,但根据TypeScript是可以的,因为这两种类型都是同一类型的别名:
type UserInputSanitizedString = string;
function sanitizeInput(str: string): UserInputSanitizedString {
return sanitize(str);
}
// Create a sanitized input
let userInput = sanitizeInput(getInput());
// Can still be re-assigned with a string though
userInput = "new input";
十、接口
接口声明是命名对象类型的另一种方式:
interface Point {
x: number;
y: number;
}
function printCoord(pt: Point) {
console.log("The coordinate's x value is " + pt.x);
console.log("The coordinate's y value is " + pt.y);
}
printCoord({ x: 100, y: 100 });
就像我们在上面使用类型别名时一样,该示例的工作方式就像我们使用匿名对象类型一样。TypeScript只关心我们传递给printCoord
的值的结构 - 它只关心它是否具有预期的属性。只关注类型的结构和功能,这就是我们称TypeScript为结构类型化类型系统的原因。
类型别名与接口的区别
类型别名和接口非常相似,在许多情况下,您可以在它们之间自由选择。interface
的几乎所有功能在type
中都可用,关键区别在于type
不能重新打开以添加新属性,而interface
始终是可扩展的。
您将在后面的章节中了解有关这些概念的更多信息,因此,如果您没有立即理解所有这些概念,请不要担心。
- 在 TypeScript 版本 4.2 之前,类型别名可能会出现在错误消息中,有时会代替等效的匿名类型(可能需要也可能不需要)。接口将始终在错误消息中命名。
- 类型别名可能不参与声明合并,但接口可以。
- 接口只能用于声明对象的形状,而不能重命名基本类型。
- 接口名称将始终以其原始形式显示在错误消息中,但仅当它们按名称使用时。
在大多数情况下,您可以根据个人喜好进行选择,TypeScript会告诉您是否需要另一种类型的声明。如果您想要启发式,请使用interface
,直到您需要使用type
中的功能
十一、类型断言
有时,您将获得有关 TypeScript 无法知道的值类型的信息。
例如,如果您使用的是 document.getElementById
,TypeScript 只知道这将返回某种 HTMLElement
,但您可能知道您的页面将始终具有具有给定 ID 的 HTMLCanvasElement
。
在此情况下,可以使用类型断言来指定更具体的类型:
const myCanvas = document.getElementById("main_canvas") as HTMLCanvasElement;
与类型批注一样,编译器会删除类型断言,并且不会影响代码的运行时行为。
您还可以使用尖括号语法(除非代码位于 .tsx 文件中),该语法等效:
const myCanvas = <HTMLCanvasElement>document.getElementById("main_canvas");
- 提醒:由于类型断言在编译时被删除,因此没有与类型断言关联的运行时检查。如果类型断言错误,则不会生成异常或 null。
TypeScript 仅允许转换为更具体或更不具体的类型的类型断言。此规则可防止”不可能”的强制,例如:
const x = "hello" as number;
// 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
,我们将在后面介绍),然后是所需的类型:
const a = (expr as any) as T;
十二、字面量类型
除了一般的类型string
和number
之外,我们还可以引用类型位置中的特定字符串和数字。
考虑这个问题的一种方法是考虑JavaScript如何以不同的方式声明变量。var
和let
都允许更改变量内部保存的内容,而const
则不允许。这反映在 TypeScript 如何为文本创建类型中。
let changingString = "Hello World";
changingString = "Olá Mundo";
// Because `changingString` can represent any possible string, that
// is how TypeScript describes it in the type system
changingString;
// let changingString: string
const constantString = "Hello World";
// Because `constantString` can only represent 1 possible string, it
// has a literal type representation
constantString;
// const constantString: "Hello World"
就其本身而言,文字类型并不是很有价值:
let x: "hello" = "hello";
// OK
x = "hello";
// ...
x = "howdy";
// Type '"howdy"' is not assignable to type '"hello"'.
拥有一个只能有一个值的变量没有多大用处!
但是,通过组合字面量类型到 union,您可以表达一个更有用的概念 - 例如,仅接受一组特定已知值的函数:
function printText(s: string, alignment: "left" | "right" | "center") {
// ...
}
printText("Hello, world", "left");
printText("G'day, mate", "centre");
// Argument of type '"centre"' is not assignable to parameter of type '"left" | "right" | "center"'.
数字字面量类型的使用原理类似
function compare(a: string, b: string): -1 | 0 | 1 {
return a === b ? 0 : a > b ? 1 : -1;
}
当然,也能组合非字面量类型
interface Options {
width: number;
}
function configure(x: Options | "auto") {
// ...
}
configure({ width: 100 });
configure("auto");
configure("automatic");
// Argument of type '"automatic"' is not assignable to parameter of type 'Options | "auto"'.
还有一种字面量类型:布尔字面量。只有两种布尔文本类型,正如您可能猜到的那样,它们是true
和false
类型。boolean
类型本身实际上就是联合类型true | false
。
字面量推断
使用对象初始化变量时,TypeScript 假定该对象的属性以后可能会更改值。例如,如果您编写了如下代码:
const obj = { counter: 0 };
if (someCondition) {
obj.counter = 1;
}
TypeScript 不假定将 1 分配给以前具有 0 的字段是一个错误。另一种说法是,obj.counter 必须具有类型编号,而不是 0,因为类型用于确定读取和写入行为。
这同样适用于字符串:
const req = { url: "https://example.com", method: "GET" };
handleRequest(req.url, req.method);
// Argument of type 'string' is not assignable to parameter of type '"GET" | "POST"'.
在上面的示例中,req.method
被推断为字符串,而不是"GET"
。因为代码可以在创建 req 和调用 handleRequest 之间进行计算,后者可以将一个新字符串(如"GUESS"
)分配给 req.method
,所以 TypeScript 认为此代码有错误。
有两种方法可以解决此问题。
- 您可以通过在任一位置添加类型断言来更改推理:
// Change 1:
const req = { url: "https://example.com", method: "GET" as "GET" };
// Change 2
handleRequest(req.url, req.method as "GET");
- 更改 1 表示”我打算让
req.method
始终具有文字类型"GET"
,从而防止在之后可能将"GUESS"
分配给该字段。更改 2 表示”由于其他原因,我知道 req.method 的值为"GET"
。
- 您可以使用 const 将整个对象转换为类型文本:
const req = { url: "https://example.com", method: "GET" } as const;
handleRequest(req.url, req.method);
as const
后缀的作用类似于 const,但对于类型系统,确保为所有属性分配文本类型,而不是更常规的版本(如string
或number
)。
十三、null
和undefined
JavaScript 有两个基本类型值,用于表示不存在或未初始化的值:null
和undefined
。
TypeScript 具有两个同名的相应类型。这些类型的行为方式取决于您是否启用了strictNullChecks
选项。
strictNullChecks
off
关闭 strictNullChecks 后,仍可正常访问可能为 null 或未定义的值,并且可以将 null 和 undefined 值分配给任何类型的属性。这类似于没有空检查的语言(例如.C#,Java)的行为方式。缺乏对这些值的检查往往是错误的主要来源;我们总是建议人们打开strictNullChecks
,如果在他们的代码库中这样做是可行的。
strictNullChecks
on
启用 strictNullChecks 后,当值为 null 或未定义时,您需要先测试这些值,然后再对该值使用方法或属性。就像在使用可选属性之前检查未定义一样,我们可以使用缩小范围来检查可能为 null 的值:
function doSomething(x: string | null) {
if (x === null) {
// do nothing
} else {
console.log("Hello, " + x.toUpperCase());
}
}
非空断言运算符(后缀!
)
TypeScript 还具有一种特殊语法,用于从类型中删除 null 和 undefined,而无需执行任何显式检查。写!
在任何表达式之后表示一个类型断言值不为null
或undefined
:
function liveDangerously(x?: number | null) {
// No error
console.log(x!.toFixed());
}
就像其他类型断言一样,这不会更改代码的运行时行为,因此仅使用!
当您知道该值不能为空或未定义时。
十四、枚举
枚举是由TypeScript添加到JavaScript中的一项功能,它允许描述一个值,该值可能是一组可能的命名常量之一。与大多数TypeScript功能不同,这不是对JavaScript的类型级添加,而是添加到语言和运行时中的东西。因此,这是您应该知道存在的功能,但除非您确定,否则可能会推迟使用。可以在枚举参考页中阅读有关枚举的详细信息。
十五、不太常见的基本类型
值得一提的是JavaScript中在类型系统中表示的其余基本类型。虽然我们不会在这里深入探讨。
bigint
从ES2020开始,JavaScript中有一个用于非常大的整数的原语,BigInt:
// Creating a bigint via the BigInt function
const oneHundred: bigint = BigInt(100);
// Creating a BigInt via the literal syntax
const anotherHundred: bigint = 100n;
您可以在 TypeScript 3.2 发行说明 中了解有关 BigInt 的更多信息。
symbol
JavaScript 中有一个基本类型,用于通过函数 Symbol() 创建全局唯一引用:
const firstName = Symbol("name");
const secondName = Symbol("name");
if (firstName === secondName) {
// This condition will always return 'false' since the types 'typeof firstName' and 'typeof secondName' have no overlap.
// Can't ever happen
}
您可以在 Symbols 参考页面中了解有关它们的更多信息。
十六、参考链接
常用类型:https://www.typescriptlang.org/docs/handbook/2/everyday-types.html