外观
TypeScript 笔记
约 2841 字大约 9 分钟
2025-08-16
一、TypeScript 基础
1.1 什么是 TypeScript?
TypeScript 是 JavaScript 的超集,添加了可选的静态类型系统。它最终会被编译成纯 JavaScript,可以在任何支持 JavaScript 的环境中运行。TypeScript 的主要目标是:
- 提供静态类型检查,提前发现潜在错误
- 增强代码可读性和可维护性
- 提供更好的开发工具支持(如自动补全、重构等)
- 支持最新的 ECMAScript 特性,同时保持向后兼容
1.2 为什么使用 TypeScript?
- 减少运行时错误:编译时就能捕获很多错误
- 增强代码可读性:类型注解本身就是文档
- 提高开发效率:IDE 提供更准确的代码提示和自动补全
- 更好的重构支持:类型系统让重构更安全可靠
- 渐进式采用:可以逐步将 JavaScript 项目迁移到 TypeScript
1.3 基本类型
布尔值
let isDone: boolean = false;数字
let decimal: number = 6;
let hex: number = 0xf00d;
let binary: number = 0b1010;
let octal: number = 0o744;字符串
let color: string = "blue";
color = 'red';
// 模板字符串
let fullName: string = `Bob Bobbington`;
let age: number = 37;
let sentence: string = `Hello, my name is ${fullName}. I'll be ${age + 1} years old next month.`;数组
// 类型后加[]
let list: number[] = [1, 2, 3];
// 泛型语法
let list2: Array<number> = [1, 2, 3];元组
// 元组类型允许表示一个已知元素数量和类型的数组
let x: [string, number];
x = ['hello', 10]; // OK
x = [10, 'hello']; // Error枚举
enum Color {Red, Green, Blue}
let c: Color = Color.Green;
// 自定义值
enum Color {Red = 1, Green = 2, Blue = 4}
let colorName: string = Color[2]; // 'Green'any
// any 类型可以表示任何类型
let notSure: any = 4;
notSure = "maybe a string instead";
notSure = false; // okay, definitely a boolean
// 不推荐过度使用 any,会失去类型检查的优势void
// void 表示没有任何类型,通常用于函数返回值
function warnUser(): void {
console.log("This is my warning message");
}
// 声明一个 void 类型的变量没有什么大用,因为你只能为它赋予 undefined 或 null
let unusable: void = undefined;null 和 undefined
// 默认情况下,undefined 和 null 是所有类型的子类型
let u: undefined = undefined;
let n: null = null;
// 在 strictNullChecks 模式下,它们只能赋值给 void 和它们各自never
// never 类型表示那些永不存在的值的类型
// 返回 never 的函数必须存在无法达到的终点
function error(message: string): never {
throw new Error(message);
}
// 推断的返回值类型为 never
function fail() {
return error("Something failed");
}object
// object 表示非原始类型
declare function create(o: object | null): void;
create({ prop: 0 }); // OK
create(null); // OK
create(42); // Error
create("string"); // Error二、类型推断与类型注解
2.1 类型推断
TypeScript 会自动推断变量的类型,无需显式注解:
let message = "Hello"; // 推断为 string
let count = 10; // 推断为 number
let isActive = true; // 推断为 boolean
let items = [1, 2, 3]; // 推断为 number[]最佳实践:
- 对于函数参数和返回值,建议添加类型注解
- 对于对象字面量,TypeScript 能很好地推断其结构
- 对于简单变量,可以依赖类型推断
2.2 类型注解
// 变量类型注解
let username: string = "John";
// 函数参数和返回值类型注解
function add(a: number, b: number): number {
return a + b;
}
// 对象类型注解
let user: { id: number; name: string } = {
id: 1,
name: "John"
};三、接口与类型别名
3.1 接口(Interface)
基本用法
interface User {
id: number;
name: string;
email?: string; // 可选属性
readonly age: number; // 只读属性
}
const user: User = {
id: 1,
name: "John",
age: 30
};
user.age = 31; // Error: cannot assign to 'age' because it is a read-only property函数类型接口
interface SearchFunc {
(source: string, subString: string): boolean;
}
let mySearch: SearchFunc;
mySearch = function(source: string, subString: string): boolean {
return source.search(subString) !== -1;
};可索引类型
interface StringArray {
[index: number]: string;
}
let myArray: StringArray = ["Bob", "Fred"];
let secondItem: string = myArray[1]; // Fred类类型接口
interface ClockInterface {
currentTime: Date;
setTime(d: Date): void;
}
class Clock implements ClockInterface {
currentTime: Date = new Date();
setTime(d: Date) {
this.currentTime = d;
}
constructor(h: number, m: number) { }
}3.2 类型别名(Type Alias)
基本用法
type Point = {
x: number;
y: number;
};
let point: Point = { x: 10, y: 20 };联合类型
type ID = number | string;
let userId: ID = 123;
userId = "abc123"; // OK交叉类型
type Name = { name: string };
type Age = { age: number };
type Person = Name & Age;
let person: Person = { name: "John", age: 30 };类型别名 vs 接口
- 接口:更适合描述对象的形状,支持声明合并
- 类型别名:可以描述更复杂的类型(联合、交叉等),不能声明合并
实用场景:
- 对象结构:优先使用接口
- 联合类型:必须使用类型别名
- 简单类型:两者皆可,保持项目一致性
四、类
4.1 类的基本用法
class Person {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
greet() {
console.log(`Hello, my name is ${this.name} and I'm ${this.age} years old.`);
}
}
const john = new Person("John", 30);
john.greet();4.2 访问修饰符
class Person {
public name: string; // 默认为 public
private email: string; // 只能在类内部访问
protected address: string; // 可在类和子类中访问
constructor(name: string, email: string, address: string) {
this.name = name;
this.email = email;
this.address = address;
}
// public 方法
getPrivateInfo() {
return this.email; // OK
}
}
class Student extends Person {
constructor(name: string, email: string, address: string, public studentId: string) {
super(name, email, address);
}
getAddress() {
return this.address; // OK, protected 可在子类中访问
}
}4.3 参数属性
class Person {
// 简化写法,自动创建并初始化属性
constructor(public name: string, private age: number) {}
getAge() {
return this.age;
}
}4.4 存取器(Getter/Setter)
class Employee {
private _fullName: string;
get fullName(): string {
return this._fullName;
}
set fullName(newName: string) {
if (newName && newName.length > 10) {
throw new Error("Full name has a max length of 10.");
}
this._fullName = newName;
}
}
let employee = new Employee();
employee.fullName = "Bob Smith"; // 使用 setter
if (employee.fullName) { // 使用 getter
console.log(employee.fullName);
}4.5 静态属性
class Grid {
static origin = {x: 0, y: 0};
calculateDistanceFromOrigin(point: {x: number; y: number}) {
let xDist = (point.x - Grid.origin.x);
let yDist = (point.y - Grid.origin.y);
return Math.sqrt(xDist * xDist + yDist * yDist) / this.scale;
}
constructor(public scale: number) { }
}
let grid1 = new Grid(1.0); // 1x scale
let grid2 = new Grid(5.0); // 5x scale4.6 抽象类
abstract class Department {
constructor(public name: string) {}
printName(): void {
console.log("Department name: " + this.name);
}
abstract printMeeting(): void; // 必须在派生类中实现
}
class AccountingDepartment extends Department {
constructor() {
super("Accounting and Auditing"); // 在派生类的构造函数中必须调用 super()
}
printMeeting(): void {
console.log("The Accounting Department meets each Monday at 10am.");
}
generateReports(): void {
console.log("Generating accounting reports...");
}
}五、泛型
5.1 为什么需要泛型?
泛型允许我们创建可重用的组件,同时保持类型安全。
// 非泛型函数,失去类型信息
function identity(arg: any): any {
return arg;
}
// 泛型函数,保留类型信息
function identity<T>(arg: T): T {
return arg;
}
// 使用
let output = identity<string>("myString"); // 推断为 string
let output2 = identity("myString"); // 类型推断为 string5.2 泛型函数
// 基本泛型函数
function identity<T>(arg: T): T {
return arg;
}
// 多个类型参数
function extend<T, U>(first: T, second: U): T & U {
return { ...first, ...second };
}
// 泛型约束
interface Lengthwise {
length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length); // OK
return arg;
}
loggingIdentity({ length: 10, value: 3 }); // OK
loggingIdentity(3); // Error5.3 泛型接口
interface GenericIdentityFn<T> {
(arg: T): T;
}
function identity<T>(arg: T): T {
return arg;
}
let myIdentity: GenericIdentityFn<number> = identity;5.4 泛型类
class GenericNumber<T> {
zeroValue: T;
add: (x: T, y: T) => T;
}
let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function(x, y) { return x + y; };5.5 泛型实用技巧
// keyof 操作符
type Point = { x: number; y: number };
type P = keyof Point; // "x" | "y"
// typeof 操作符
function add(x: number, y: number) {
return x + y;
}
const myAdd: typeof add = function(x, y) {
return x + y;
};
// 映射类型
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
type Partial<T> = {
[P in keyof T]?: T[P];
};六、模块与命名空间
6.1 ES 模块系统
TypeScript 完全支持 ES6 模块语法。
导出模块
// math.ts
export const PI = 3.1415926;
export function add(a: number, b: number): number {
return a + b;
}
export class Calculator {
// ...
}
// 默认导出(一个模块只能有一个)
export default function divide(a: number, b: number): number {
return a / b;
}导入模块
// app.ts
import { PI, add, Calculator } from './math';
import divide from './math'; // 导入默认导出
// 重命名导入
import { add as sum } from './math';
// 导入所有
import * as math from './math';
// 只执行模块,不导入任何绑定
import './analytics';
// 动态导入
button.addEventListener('click', () => {
import('./lazy-module')
.then(module => {
module.doSomething();
});
});6.2 模块最佳实践
- 优先使用命名导出,便于重命名导入
- 为库提供默认导出,便于用户快速使用
- 避免混合使用默认导出和命名导出
- 使用相对路径导入本地模块,使用模块名导入第三方库
七、实用技巧与高级类型
7.1 类型断言
// 方式一:as 语法(推荐,与JSX兼容)
let someValue: any = "this is a string";
let strLength: number = (someValue as string).length;
// 方式二:<type> 语法
let strLength2: number = (<string>someValue).length;7.2 非空断言操作符
// 告诉 TypeScript 一个值不可能是 null 或 undefined
function processValue(value: string | null | undefined) {
// 使用 ! 告诉编译器 value 不为空
console.log(value!.toUpperCase());
}7.3 可选链操作符
// 安全访问嵌套属性
interface User {
profile?: {
address?: {
city?: string;
};
};
}
const user: User = {};
const city = user.profile?.address?.city; // undefined,不会出错
// 用于函数调用
user.profile?.getAddress?.();7.4 空值合并运算符
// 仅当左侧为 null 或 undefined 时使用右侧值
const username = userInput ?? 'guest';
// 与 || 的区别
const count = 0;
const defaultCount = count || 10; // 10
const nullCount = count ?? 10; // 07.5 类型守卫
// typeof 类型守卫
function padLeft(value: string, padding: string | number) {
if (typeof padding === "number") {
return Array(padding + 1).join(" ") + value;
}
if (typeof padding === "string") {
return padding + value;
}
throw new Error(`Expected string or number, got '${padding}'.`);
}
// instanceof 类型守卫
if (input instanceof Date) {
// input 被缩小为 Date 类型
}
// 自定义类型守卫
interface Fish { swim: () => void; }
interface Bird { fly: () => void; }
function isFish(pet: Fish | Bird): pet is Fish {
return (pet as Fish).swim !== undefined;
}
if (isFish(pet)) {
pet.swim();
} else {
pet.fly();
}7.6 条件类型
// 条件类型
type NonNullable<T> = T extends null | undefined ? never : T;
// 预定义的条件类型
type NonNull = NonNullable<string | null | undefined>; // string
// infer 关键字
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any;八、TypeScript 配置
8.1 tsconfig.json 重要配置
{
"compilerOptions": {
"target": "ES2015", // 指定 ECMAScript 目标版本
"module": "commonjs", // 指定模块代码生成
"strict": true, // 启用所有严格类型检查选项
"esModuleInterop": true, // 允许 default 导入
"skipLibCheck": true, // 跳过声明文件的类型检查
"outDir": "./dist", // 输出目录
"rootDir": "./src", // 输入文件目录
"moduleResolution": "node", // 模块解析策略
"sourceMap": true // 生成对应的 .map 文件
},
"include": ["src/**/*"] // 需要编译的文件
}8.2 常用配置项说明
- strict:启用所有严格类型检查选项(推荐开启)
- esModuleInterop:允许从 CommonJS 模块中使用 default 导入
- skipLibCheck:跳过声明文件的类型检查,提高编译速度
- noImplicitAny:在表达式和声明上有隐含的 'any' 类型时报错
- strictNullChecks:启用严格的 null 检查
九、最佳实践
9.1 类型设计原则
- 优先使用接口:当描述对象结构时,优先使用接口
- 避免过度使用 any:会失去类型检查的优势
- 合理使用类型推断:不需要为每个变量都添加类型注解
- 使用联合类型代替 any:当有多个可能类型时
- 使用类型别名描述复杂类型:如联合类型、交叉类型等
9.2 代码组织
- 模块化开发:将相关功能组织在单独的模块中
- 合理使用命名空间:仅在必要时使用,优先使用 ES 模块
- 类型定义分离:复杂类型可以单独定义,提高可读性
- 保持类型简洁:避免过度复杂的类型定义
9.3 与 JavaScript 混合使用
- 渐进式迁移:可以逐步将 JavaScript 项目迁移到 TypeScript
- 使用 .d.ts 文件:为 JavaScript 库添加类型定义
- 配置 allowJs:允许在 TypeScript 项目中包含 JavaScript 文件
- 配置 checkJs:对 JavaScript 文件进行类型检查
