ts随笔:基础与类型系统
本文主要记录一些关于 TypeScript 的 API 使用方法和一些底层的原理。
供自己以后查漏补缺,也欢迎同道朋友交流学习。
原文地址
介绍
TypeScript 是一种由微软开发的开源编程语言,它是 JavaScript 的一个超集,即 TypeScript 添加了额外的类型定义和一些其他功能,最终会被编译成普通的 JavaScript 代码。它旨在解决 JavaScript 在大型应用程序开发中遇到的不足,如缺少静态类型检查、接口、类以及模块等特性。由于其提供的额外安全性和开发效率提升,TypeScript 越来越受到开发者的欢迎。
特点
TypeScript 的主要特点包括:
静态类型检查:在编译阶段就能发现类型错误,有助于开发者提前发现并修正错误。类和接口:支持面向对象编程,可以定义类、继承和接口,提高代码的复用性和结构化。模块:通过导入和导出语句支持代码的模块化,有利于组织和管理大型项目。泛型:允许创建可重用的组件,这些组件可以在不指定具体类型的前提下操作多种数据类型。装饰器:提供了一种在类声明、方法、访问器、属性或参数上添加元数据或修改类的行为的方式。
安装和配置
安装 TypeScript
-
前提条件:确保你的系统已安装 Node.js,因为 TypeScript 依赖于 Node.js 的 npm 包管理器进行安装。
-
全局安装 TypeScript:打开终端或命令提示符,运行以下命令来全局安装 TypeScript:
bash
## 使用 npm 全局安装
npm install -g typescript
## 或使用 yarn 全局安装
yarn global add typescript
## 验证 TypeScript 是否安装成功
tsc --version
创建 TypeScript 项目
bash
## 初始化项目:在你希望创建项目的目录下,运行下面命令
npm init
## 安装依赖
npm install --save-dev typescript
配置 TypeScript
bash
## 在项目根目录下创建 tsconfig.json
npx tsc --init
配置完成后,你可以在 tsconfig.json 中设置目标 JavaScript 版本(target)、模块系统(module)、源码目录(include)、排除目录(exclude)等。
json
{
"compilerOptions": {
"target": "es2020",
"module": "esnext",
"strict": true,
"esModuleInterop": true,
"outDir": "./dist",
"sourceMap": true
},
"include": ["./src/**/*"],
"exclude": ["node_modules"]
}
随着 ES2025、ES2026 等新标准推进,TypeScript 的 target 和 lib 选项也会不断跟进最新的内置对象和语法特性(例如 Iterator Helpers、Set 扩展、JSON 模块、Temporal 等),只要保持 TypeScript 版本和编译目标的升级,就可以在项目中获得这些新能力的类型支持。
编译 TypeScript
bash
npx tsc
这将根据 tsconfig.json 中的配置编译你的 TypeScript 文件,并将编译后的 JavaScript 文件输出到指定的目录。
基础
内置对象
TypeScript 继承了 ECMAScript 的所有内置对象。以下是一些主要的内置对象:
Boolean:布尔值对象,用于封装一个布尔值。Error:错误对象,用于生成错误信息。Date:日期对象,处理日期和时间。RegExp:正则表达式对象,用于匹配文本模式。Number:数值对象,封装数字并提供相关操作方法。String:字符串对象,封装字符串并提供操作字符串的方法。
如果在浏览器环境中使用 TypeScript,还会有额外的针对 DOM 和 BOM 的内置对象:
Document:代表整个 HTML 文档的对象。HTMLElement:基类,表示任何 HTML 元素。Event:事件对象,封装了发生的事件信息。NodeList:非实时集合,包含了按照指定选择器选取的一组节点。
在 TypeScript 中,你不需要特别引入这些内置对象,它们可以直接在代码中使用。例如,创建一个错误对象可以简单地写作:
typescript
const error = new Error();
从 ES2025、ES2026 开始,诸如 Temporal(全新的日期时间 API)、Iterator Helpers(for-of 可用的一组迭代器辅助方法)、Set 扩展方法等也会逐步进入主流运行时环境。TypeScript 会在对应的 lib.*.d.ts 中为这些内置对象补充类型定义,只要升级 TypeScript 并将 target/lib 指向更新的 ES 版本,就可以在编辑器中获得这些最新内置对象的完整类型提示。
原始数据类型
在 TypeScript 中,使用原始数据类型的基本方法如下:
typescript
// 布尔值(boolean)
const isDone: boolean = false;
// 数值(number)
const decimal: number = 6; // 十进制
const binary: number = 0b1010; // 二进制
const octal: number = 0o744; // 八进制
const hex: number = 0xf00d; // 十六进制
// 字符串(string)
const name: string = "niunai";
const fullName: string = `niunai, age is ${32}`;
// 空值(null)
const status: null | undefined = null;
// 未定义(undefined)
const u: undefined = undefined;
// 符号(Symbol)
const sym1: symbol = Symbol();
const sym2: symbol = Symbol("key");
// 大整数(BigInt)(用于表示大于 2^53 - 1 的整数)
const bigInt: bigint = 1020023339011239123n; // 注意结尾的 n
随着 Temporal 在 ES2025 附近进入标准,你在处理日期和时间时可以使用更加精确和可靠的类型,例如:
typescript
// 假设运行时和 TypeScript lib 已经支持 Temporal
// 下面的类型定义就可以通过官方声明文件获得提示
// const now: Temporal.ZonedDateTime = Temporal.Now.zonedDateTimeISO();
任意值 any
在 TypeScript 中,any 类型是一个特殊类型,表示可以是任何类型。当你不清楚一个值的具体类型,或者希望跳过类型检查时,可以使用 any 类型。但是,过度使用 any 会削弱类型安全优势,因此应谨慎使用。
typescript
declare function someFunction(): any;
const anyValue: any = someFunction();
数组类型
在 TypeScript 中,数组类型可以明确指定每个元素的类型,以增强类型安全。有几种不同的方式来定义数组类型:
typescript
// 类型后缀表示法(最常用)
const numberArray: number[] = [1, 2, 3];
const stringArray: string[] = ["a", "b", "c"];
// 泛型数组类型 Array<T>
const numberArray2: Array<number> = [1, 2, 3];
const stringArray2: Array<string> = ["a", "b", "c"];
// 元组类型 Tuple
const tuple: [string, number, boolean] = ["hello", 11, true];
// 只读数组 ReadonlyArray<T>
const readonlyArray: ReadonlyArray<number> = [1, 2, 3];
// readonlyArray.push(4); // 这会导致编译错误
// 类型推断
const mixedArray = [1, "two", true]; // 推断为 (number | string | boolean)[]
const explicitlyTypedArray: (number | string)[] = [1, "two"]; // 明确指定联合类型数组
在未来 ES2025/ES2026 的 Iterator Helpers 提案落地后,数组和可迭代对象会多出一批链式调用方法,例如 map、filter、take 等。TypeScript 会通过更精细的泛型定义,对这些方法的返回值进行类型推断,从而在保持链式风格的同时保证类型安全。
函数类型
函数类型用于描述函数的输入(参数类型)和输出(返回值类型)。定义函数类型主要有两种方式:匿名函数类型和具名函数声明。
匿名函数类型的定义
匿名函数类型直接定义了函数参数和返回值的类型,不绑定到特定的函数名。这种类型通常用于类型注解或变量声明中。
typescript
// 函数类型定义
let add: (x: number, y: number) => number;
// 实现该类型的函数赋值给变量
add = function (x: number, y: number): number {
return x + y;
};
具名函数声明
具名函数声明不仅定义了函数的类型,还为函数赋予了一个名字,可以在代码中直接调用。
typescript
function add2(x: number, y: number): number {
return x + y;
}
这里,add2 函数被明确声明为接收两个 number 参数并返回 number 类型值的函数。
函数重载
TypeScript 还支持 函数重载,允许为同一个函数名提供多个类型签名,根据传入参数的不同自动匹配合适的实现。
typescript
function print(value: string): void;
function print(value: number): void;
function print(value: string | number): void {
console.log(value);
}
print("Hello");
print(123);
对象类型 - 接口 interface
接口是 TypeScript 中定义和强制执行对象结构的强大工具。它们不仅能够确保代码中对象的正确性,还能帮助文档化代码,提升开发者的理解速度和代码的可维护性。
基本接口定义
typescript
interface Person {
firstName: string;
lastName: string;
age?: number; // 问号表示此属性是可选的
}
// 使用接口
let person: Person = {
firstName: "John",
lastName: "Doe",
};
扩展接口
接口还可以扩展其他接口,实现接口的复用和层次化定义:
typescript
interface Employee extends Person {
employeeId: number;
department: string;
}
// 使用扩展的接口
let employee: Employee = {
firstName: "Jane",
lastName: "Doe",
employeeId: 123,
department: "HR",
};
函数类型接口
接口也可以用来定义函数的参数类型或返回值类型:
typescript
interface GreetingFunc {
(name: string): string;
}
let sayHello: GreetingFunc = function (name: string): string {
return `Hello, ${name}!`;
};
索引签名
接口可以使用索引签名定义可索引的对象,比如数组或字典类型:
typescript
interface StringArray {
[index: number]: string;
}
let myArray: StringArray = ["Bob", "Alice"];
Type 类型别名
在 TypeScript 中,type 是另一种创建新类型的手段,与 interface 有着相似之处,但也存在关键区别。type 主要用于定义 类型别名(Type Aliases)、联合类型(Union Types)、元组类型(Tuple Types)、字面量类型(Literal Types)以及 枚举(Enums,虽然通常直接使用 enum 关键字)。
类型别名(Type Aliases)
类型别名用于给已存在的类型起一个新的名字,增加代码的可读性。它可以用来替代任何类型,包括基本类型、接口、联合类型等。
typescript
type StringOrNumber = string | number;
function logValue(value: StringOrNumber) {
console.log(value);
}
logValue("Hello");
logValue(123);
联合类型(Union Types)
联合类型表示一个值可以是几种类型之一。使用 type 定义联合类型可以更易于阅读。
typescript
type UserID = string | number;
function getUser(id: UserID) {
// ...
}
元组类型(Tuple Types)
元组类型允许表示一个已知元素数量和类型的数组。使用 type 定义元组类型可以提供更清晰的意图。
typescript
type Coordinate = [number, number];
const point: Coordinate = [10, 20];
字面量类型(Literal Types)
字面量类型允许你指定一个具体的值作为类型,这对于精确控制变量的可能值很有用。
typescript
type Color = "red" | "green" | "blue";
function setBackgroundColor(color: Color) {
// ...
}
setBackgroundColor("red"); // 正确
// setBackgroundColor("yellow"); // 错误
与接口的区别
- interface 更侧重于描述对象结构,可以被实现(通过类),可以扩展,适合描述具有相同属性或方法的多个实体的公共结构。
- type 提供了更多关于类型定义的灵活性,可以用于联合类型、元组、字面量类型等,不支持实现或扩展,但可以被其他类型别名再次引用。
枚举 enum
在 TypeScript 中,枚举(Enum)是一种数值或字符串常量的集合,它为一组相关的值提供了友好的名字。
typescript
// 基本枚举:默认情况下,第一个成员的值为 0,之后每个成员依次递增 1。
enum Color {
Red,
Green,
Blue,
}
console.log(Color.Red); // 输出 0
console.log(Color[0]); // 输出 "Red"
// 指定成员值
enum Color2 {
Red = 8,
Green = 16,
Blue = 32,
}
console.log(Color2.Red); // 输出 8
// 字符串枚举
enum Color3 {
Red = "RED",
Green = "GREEN",
Blue = "BLUE",
}
console.log(Color3.Red); // 输出 "RED"
元组 Tuple
在 TypeScript 中,元组(Tuple)是一种特殊的数据类型,它允许你创建一个固定长度的数组,其中每个元素可以有不同的类型。
typescript
// 基本定义
let myTuple: [string, number, boolean];
// 初始化和访问
myTuple = ["Alice", 30, true];
console.log(myTuple[0]); // "Alice"
console.log(myTuple[1]); // 30
// 解构赋值
let [userName, age, isAdmin] = myTuple;
console.log(userName);
console.log(age);
console.log(isAdmin);
// 可选元素与默认值
let optionalTuple: [string, number?, boolean] = ["Bob"];
// 组合元组
let firstTuple: [string, number] = ["Tom", 25];
let secondTuple: [boolean] = [true];
let combinedTuple: [...firstTuple, ...secondTuple] = ["Tom", 25, true];
类型断言
类型断言(Type Assertion)是一种告诉编译器"我相信这个值是某种类型"的方式。它不会修改变量的实际类型,只是让编译器按照你所断言的类型去处理这个值。
typescript
let someValue = "this is a string";
let strLength1: number = (someValue as string).length;
let strLength2: number = (<string>someValue).length;
在实际项目中,随着 2025/2026 年新的 Web 与语言特性落地,你经常会在类型声明还不完全的时候先使用类型断言"兜底",待官方声明文件补全之后再逐步收紧类型。
声明文件
声明文件(.d.ts 文件)是 TypeScript 与非 JavaScript 代码交互的关键桥梁,它们使得 TypeScript 能够为 JavaScript 代码提供类型检查和智能提示,提升开发体验和代码质量。
声明文件的用途
- 提供类型信息:对于没有类型注释的 JavaScript 库,声明文件提供必要的类型信息,让 TypeScript 知道函数、对象、模块的形状。
- 兼容性:允许 TypeScript 项目无痛地引用和使用 JavaScript 库,保持代码的强类型检查和智能提示。
- 定义全局变量和模块:声明文件可以用来定义全局变量(如 window 上的挂载点)或声明外部模块的存在。
如何创建和使用声明文件
创建自定义声明文件:
typescript
// myLib.d.ts
declare function myFunction(name: string): void;
export default myFunction;
使用第三方声明文件:
bash
npm install @types/node --save-dev
声明全局变量:
typescript
declare var jQuery: any;
声明模块:
typescript
declare module "myModule" {
export function doSomething(): string;
}
2025-2026 新模块与声明文件
随着 ES2025 的 JSON 模块、ES2026 的 import defer 等提案逐步标准化,运行时会出现更多"非传统脚本文件"的导入方式,例如:
typescript
// 假设运行时已经原生支持 JSON 模块
import config from "./config.json" with { type: "json" };
// 假设浏览器开始支持 import defer 语法
import defer "./heavy-module.js";
对于这些新特性,对应的类型信息会由 TypeScript 团队在 lib.d.ts、dom.d.ts 等声明文件中同步维护。项目只要升级到较新的 TypeScript 版本,并合理配置 module、moduleResolution,就可以在编辑器中获得这些新模块语法的类型提示和错误检查。