
基础
约 2970 字大约 10 分钟
2025-01-21
前言
TypeScript 是 具有类型语法的 JavaScript。TypeScript 是一种基于 JavaScript 构建的强类型编程语言,可为你提供任何规模的更好工具。
- TypeScript 向 JavaScript 添加了额外的语法,以支持与你的编辑器更紧密的集成。 在编辑器中尽早发现错误。
- TypeScript 代码转换为 JavaScript,它在 JavaScript 运行的任何地方运行:在浏览器中、在 Node.js、Deno 或 Bun 上以及在你的应用程序中。
- TypeScript 理解 JavaScript 并使用类型推断为你提供出色的工具,而无需额外的代码。
基础知识
静态类型检查
理想情况下,我们可以有一个工具来帮助我们在代码运行之前找到这些错误。这就是像 TypeScript 这样的静态类型检查器所做的。静态类型系统描述了当我们运行程序时我们的值的形状和行为。像 TypeScript 这样的类型检查器使用这些信息并告诉我们什么时候事情可能会出轨。
降级
默认情况下,TypeScript 以 ES5 为目标,这是 ECMAScript 的一个非常老的版本。通过使用 target 选项,我们可以选择更新一点的东西。使用 --target es2015 运行将 TypeScript 更改为以 ECMAScript 2015 为目标,这意味着代码应该能够在任何支持 ECMAScript 2015 的地方运行。所以运行 tsc --target es2015 hello.ts 会给我们以下输出:
noImplicitAny
回想一下,在某些地方,TypeScript 不会尝试为我们推断类型,而是退回到最宽松的类型:any。这还不是最糟糕的事情 - 毕竟,回退到 any 就是简单的 JavaScript 体验。
然而,使用 any 通常会破坏使用 TypeScript 的初衷。你的程序类型越多,你获得的验证和工具就越多,这意味着你在编写代码时遇到的错误就越少。打开 noImplicitAny 标志将对任何类型隐式推断为 any 的变量触发错误。
strictNullChecks
默认情况下,像 null 和 undefined 这样的值可以分配给任何其他类型。这可以使编写一些代码变得更容易,但是忘记处理 null 和 undefined 是世界上无数错误的原因 - 有些人认为它是 十亿美元的错误!strictNullChecks 标志使处理 null 和 undefined 更加明确,让我们不必担心是否忘记处理 null 和 undefined。
日常类型
基础类型:string、number 和 boolean
数组
要指定像 [1, 2, 3] 这样的数组类型,可以使用语法 number[];此语法适用于任何类型(例如,string[] 是一个字符串数组,等等)。你也可以看到这个写成 Array<number>
,意思是一样的。当我们介绍泛型时,我们将了解更多关于语法T<U>
的信息。
any
TypeScript 也有一个特殊的类型,any,当你不希望某个特定的值导致类型检查错误时,你可以使用它。
当一个值的类型为 any 时,你可以访问它的任何属性(这又将是 any 类型),像函数一样调用它,将它分配给(或从)任何类型的值,或者几乎任何其他东西这在语法上是合法的:
函数
函数是在 JavaScript 中传递数据的主要方式。TypeScript 允许你指定函数的输入和输出值的类型。
参数类型注解
声明函数时,可以在每个参数后面加上类型注解,声明函数接受哪些类型的参数。参数类型注释在参数名称之后:
// Parameter type annotation
function greet(name: string) {
console.log("Hello, " + name.toUpperCase() + "!!");
}
返回类型注解
你还可以添加返回类型注释。返回类型注释出现在参数列表之后:
function getFavoriteNumber(): number {
return 26;
}
与变量类型注解非常相似,你通常不需要返回类型注解,因为 TypeScript 会根据其 return 语句推断函数的返回类型。上面例子中的类型注解并没有改变任何东西。一些代码库将明确指定返回类型以用于文档目的,以防止意外更改,或仅出于个人喜好。
返回 Promise 的函数
如果你想注释一个返回 Promise 的函数的返回类型,你应该使用 Promise 类型:
async function getFavoriteNumber(): Promise<number> {
return 26;
}
对象类型
除了基础类型之外,你会遇到的最常见的类型是对象类型。这指的是任何带有属性的 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 });
联合类型
TypeScript 的类型系统允许你使用各种运算符从现有类型中构建新类型。现在我们知道如何编写几种类型,是时候开始以有趣的方式组合它们了。
定义联合类型
你可能会看到的第一种组合类型的方法是联合类型。联合类型是由两种或多种其他类型组成的类型,表示可能是这些类型中的任何一种的值。我们将这些类型中的每一种都称为联合的成员。
让我们编写一个可以对字符串或数字进行操作的函数:
function printId(id: number | string) {
console.log("Your ID is: " + id);
}
// OK
printId(101);
// OK
printId("202");
// Error
printId({ myID: 22342 });
使用联合类型
提供与联合类型匹配的值很容易 - 只需提供与任何联合成员匹配的类型即可。如果你有一个联合类型的值,你如何处理它?
TypeScript 只有在对联合的每个成员都有效的情况下才允许操作。例如,如果你有联合 string | number,则不能使用仅在 string 上可用的方法:
function printId(id: number | string) {
console.log(id.toUpperCase());
}
解决方案是用代码缩小联合,就像在没有类型注释的 JavaScript 中一样。当 TypeScript 可以根据代码的结构为某个值推断出更具体的类型时,就会发生缩小。
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);
}
}
类型别名
我们一直通过直接在类型注释中编写对象类型和联合类型来使用它们。这很方便,但通常希望多次使用同一个类型并用一个名称引用它。
类型别名正是这样的 - 任何类型的名称。类型别名的语法是:
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;
接口
接口声明是命名对象类型的另一种方式:
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 中可用,主要区别在于无法重新打开类型以添加 新属性,而接口始终可扩展。类型别名和接口的区别
类型断言
有时你会得到关于 TypeScript 无法知道的值类型的信息。
例如,如果你使用的是 document.getElementById,TypeScript 只知道这将返回某种 HTMLElement,但你可能知道你的页面将始终具有具有给定 ID 的 HTMLCanvasElement。
在这种情况下,你可以使用类型断言来指定更具体的类型:
const myCanvas = document.getElementById("main_canvas") as HTMLCanvasElement;
与类型注释一样,类型断言被编译器删除,不会影响代码的运行时行为。
你还可以使用尖括号语法(除非代码在 .tsx 文件中),它是等效的:
const myCanvas = <HTMLCanvasElement>document.getElementById("main_canvas");
字面类型
除了通用类型 string 和 number 之外,我们还可以在类型位置引用特定的字符串和数字。
考虑这一点的一种方法是考虑 JavaScript 如何使用不同的方法来声明变量。var 和 let 都允许更改变量中保存的内容,而 const 不允许。这反映在 TypeScript 如何为字面创建类型。
变量只能有一个值并没有多大用处!
但是通过将字面量组合成联合,你可以表达更有用的概念 - 例如,仅接受一组特定已知值的函数:
function printText(s: string, alignment: "left" | "right" | "center") {
// ...
}
printText("Hello, world", "left");
printText("G'day, mate", "centre");
null 和 undefined
JavaScript 有两个基础值用于表示值不存在或未初始化的值:null 和 undefined。
TypeScript 有两个对应的同名类型。这些类型的行为取决于你是否启用了 strictNullChecks 选项。
strictNullChecks 关闭
关闭 strictNullChecks,可能是 null 或 undefined 的值仍然可以正常访问,并且值 null 和 undefined 可以分配给任何类型的属性。这类似于没有空检查的语言(例如 C#、Java)的行为方式。缺乏对这些值的检查往往是错误的主要来源;如果在他们的代码库中这样做是可行的,我们总是建议人们打开 strictNullChecks。
strictNullChecks 开启
启用 strictNullChecks 时,当值为 null 或 undefined 时,你需要在对该值使用方法或属性之前测试这些值。就像在使用可选属性之前检查 undefined 一样,我们可以使用缩小来检查可能是 null 的值:
非空断言运算符(后缀 !)
TypeScript 还具有一种特殊的语法,可以在不进行任何显式检查的情况下从类型中删除 null 和 undefined。在任何表达式之后写 ! 实际上是一个类型断言,该值不是 null 或 undefined:
function liveDangerously(x?: number | null) {
// No error
console.log(x!.toFixed());
}
就像其他类型断言一样,这不会改变代码的运行时行为,所以当你知道值不能是 null 或 undefined 时,只使用 ! 很重要。
枚举
枚举是 TypeScript 添加到 JavaScript 的一项功能,它允许描述一个值,该值可能是一组可能的命名常量之一。与大多数 TypeScript 功能不同,这不是对 JavaScript 的类型级添加,而是添加到语言和运行时的东西。正因为如此,这是一个你应该知道存在的功能,但除非你确定,否则可能会推迟使用。你可以在 枚举参考页 中阅读有关枚举的更多信息。
类型操作
keyof 类型运算符
keyof 运算符采用对象类型并生成其键的字符串或数字字面联合。以下类型 P 与 type P = "x" | "y" 类型相同:
type Point = { x: number; y: number };
type P = keyof Point;
typeof 类型运算符
TypeScript 添加了一个 typeof 运算符,你可以在类型上下文中使用它来引用变量或属性的类型:
let s = "hello";
let n: typeof s;
版权所有
版权归属:tuyongtao1