跳到主要内容

核心知识点

1. TypeScript 简介

1.1 什么是 TypeScript?

TypeScript 是 JavaScript 的一个超集,它向 JavaScript 添加了静态类型系统。这意味着你可以在代码编写阶段就定义变量、函数参数和返回值的类型,并在编译时进行检查。TypeScript 代码最终会被编译成纯 JavaScript 代码,可以在任何支持 JavaScript 的环境中运行。

1.2 为什么使用 TypeScript?

  • 静态类型检查: 在编译阶段就能发现许多常见的错误(如拼写错误、类型不匹配),减少运行时 Bug。
  • 代码可读性和可维护性: 类型注解使得代码意图更清晰,便于团队协作和长期维护。
  • 更好的开发工具支持: 类型信息能让 IDE(如 VS Code)提供更智能的代码补全、重构和错误提示。
  • 访问最新 JavaScript 特性: TypeScript 允许你使用 ECMAScript 的最新特性,并可以编译到旧版本的 JavaScript 以兼容旧环境。

2. 基础类型 (Basic Types)

TypeScript 支持与 JavaScript 几乎相同的基本数据类型,并提供了明确的类型注解。

2.1 boolean

表示布尔值 truefalse

let isDone: boolean = false;
// isDone = 'hello'; // Error: Type 'string' is not assignable to type 'boolean'.

2.2 number

表示所有数字,包括整数和浮点数。

let decimal: number = 6;
let hex: number = 0xf00d;
let binary: number = 0b1010;
let octal: number = 0o744;
let score: number = 99.5;

2.3 string

表示文本数据,可以使用单引号 (')、双引号 (") 或模板字符串 (`)。

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.`;

2.4 array

表示数组,有两种定义方式:

  1. 元素类型后跟 []
  2. 使用数组泛型 Array<元素类型>
// 方式一
let list1: number[] = [1, 2, 3];
// list1.push('4'); // Error: Argument of type 'string' is not assignable to parameter of type 'number'.

// 方式二
let list2: Array<string> = ["a", "b", "c"];

2.5 tuple (元组)

元组类型允许表示一个已知元素数量和类型的数组,各元素的类型不必相同。

let x: [string, number];
x = ["hello", 10]; // OK
// x = [10, 'hello']; // Error: Type 'number' is not assignable to type 'string'. Type 'string' is not assignable to type 'number'.

console.log(x[0].substring(1)); // OK
// console.log(x[1].substring(1)); // Error: Property 'substring' does not exist on type 'number'.

// x[2] = 'world'; // Error: Tuple type '[string, number]' of length '2' has no element at index '2'. (Length is fixed unless configured differently)

2.6 enum (枚举)

枚举类型是对 JavaScript 标准数据类型的一个补充,它允许你为一组数值赋予友好的名字。

enum Color {
Red, // 默认从 0 开始
Green, // 1
Blue, // 2
}
let c: Color = Color.Green;
console.log(c); // 输出 1

enum Status {
Pending = 1,
Processing, // 自动递增为 2
Completed, // 自动递增为 3
}
let currentStatus: Status = Status.Processing;
console.log(currentStatus); // 输出 2

// 也可以通过值获取名字
let colorName: string = Color[2];
console.log(colorName); // 输出 'Blue'

2.7 any

any 类型允许你绕过编译时的类型检查。当你不确定某个值的类型时,或者它来自于动态内容(如用户输入或第三方库),可以使用 any。应尽量避免使用 any,因为它会失去 TypeScript 的类型保护优势。

let notSure: any = 4;
notSure = "maybe a string instead";
notSure = false; // okay, definitely a boolean

notSure.toFixed(); // Okay, toFixed exists (but may fail at runtime if notSure is not a number)
notSure.nonExistentMethod(); // Okay at compile time, will fail at runtime

2.8 unknown

unknownany 的类型安全对应物。任何类型的值都可以赋给 unknown,但你不能对 unknown 类型的值执行任何操作,除非你先进行了类型检查或类型断言,将其缩小(narrow down)为更具体的类型。

let maybe: unknown;
maybe = 10;
maybe = "hello";

// console.log(maybe.length); // Error: Object is of type 'unknown'.

if (typeof maybe === "string") {
// 在这个块中,TypeScript 知道 maybe 是 string 类型
console.log(maybe.length); // OK
}

// 使用类型断言
let strLength: number = (maybe as string).length; // OK, but potentially unsafe if maybe is not a string at runtime

2.9 void

void 表示没有任何类型。通常用作不返回任何值的函数的返回值类型。

function warnUser(): void {
console.log("This is my warning message");
// return undefined; // OK
// return null; // OK if strictNullChecks is false
// return "hello"; // Error: Type 'string' is not assignable to type 'void'.
}

let unusable: void = undefined; // 可以赋值为 undefined 或 null (取决于 strictNullChecks)
// unusable = null; // OK if strictNullChecks is false

2.10 null 和 undefined

默认情况下 nullundefined 是所有类型的子类型,可以赋值给任何类型。但当启用 strictNullChecks 编译选项时,nullundefined 只能赋值给 void 和它们各自的类型。通常推荐启用 strictNullChecks

// 启用 strictNullChecks: true
let u: undefined = undefined;
let n: null = null;

// let num: number = undefined; // Error
// let str: string = null; // Error

// 可以使用联合类型显式允许 null 或 undefined
let nullableString: string | null = null;
nullableString = "hello";

let undefinableNumber: number | undefined = undefined;
undefinableNumber = 100;

2.11 never

never 类型表示的是那些永不存在的值的类型。例如,never 类型是那些总是会抛出异常或根本就不会有返回值的函数表达式(如无限循环)的返回值类型。never 类型是任何类型的子类型,也可以赋值给任何类型;然而,没有类型是 never 的子类型或可以赋值给 never 类型(除了 never 本身之外)。

// Function returning never must not have a reachable end point
function error(message: string): never {
throw new Error(message);
}

// Inferred return type is never
function fail() {
return error("Something failed");
}

// Function returning never must not have a reachable end point
function infiniteLoop(): never {
while (true) {}
}

2.12 object

object 表示非原始类型,也就是除了 number, string, boolean, symbol, null, undefined 之外的类型。

declare function create(o: object | null): void;

create({ prop: 0 }); // OK
create(null); // OK
create([1, 2, 3]); // OK
create(function () {}); // OK

// create(42); // Error: Argument of type 'number' is not assignable to parameter of type 'object | null'.
// create("string"); // Error
// create(false); // Error
// create(undefined); // Error (if strictNullChecks is true)

注意: 更常用的是 interface 或具体的对象类型字面量来描述对象结构。

3. 接口 (Interfaces)

接口是 TypeScript 的核心原则之一,用于定义对象的“形状”或“契约”。它们用于类型检查,强制某些结构必须存在。

3.1 定义接口

使用 interface 关键字定义。

interface Person {
firstName: string;
lastName: string;
age: number;
}

function greet(person: Person) {
console.log(`Hello, ${person.firstName} ${person.lastName}`);
}

let user: Person = { firstName: "Jane", lastName: "User", age: 30 };
greet(user); // OK

// let badUser = { firstName: "John" }; // Error: Property 'lastName' is missing... Property 'age' is missing...
// greet(badUser);

3.2 可选属性 (Optional Properties)

属性名后加 ? 表示该属性是可选的。

interface SquareConfig {
color?: string;
width?: number;
}

function createSquare(config: SquareConfig): { color: string; area: number } {
let newSquare = { color: "white", area: 100 };
if (config.color) {
newSquare.color = config.color;
}
if (config.width) {
newSquare.area = config.width * config.width;
}
return newSquare;
}

let mySquare1 = createSquare({ color: "black" });
let mySquare2 = createSquare({ width: 20 });
let mySquare3 = createSquare({});
console.log(mySquare1, mySquare2, mySquare3);

3.3 只读属性 (Readonly Properties)

属性名前加 readonly 表示该属性只能在对象首次创建时赋值,之后不能修改。

interface Point {
readonly x: number;
readonly y: number;
}

let p1: Point = { x: 10, y: 20 };
// p1.x = 5; // Error: Cannot assign to 'x' because it is a read-only property.

3.4 函数类型 (Function Types)

接口也可以描述函数类型。

interface SearchFunc {
(source: string, subString: string): boolean;
}

let mySearch: SearchFunc;
mySearch = function (src, sub) {
// 参数名不需要匹配
let result = src.search(sub);
return result > -1;
};

console.log(mySearch("hello world", "world")); // true

3.5 可索引类型 (Indexable Types)

接口可以描述那些能够通过索引得到的类型,如数组和对象。

interface StringArray {
[index: number]: string; // 索引签名:数字索引返回字符串
}

let myArray: StringArray;
myArray = ["Bob", "Fred"];
let myStr: string = myArray[0]; // OK

interface Dictionary {
[key: string]: any; // 索引签名:字符串索引返回任意类型
length: number; // 可以有其他确定名称的属性
// name: string; // Error: Property 'name' of type 'string' is not assignable to 'string' index type 'any'.
// (If index signature type is more specific, e.g., string, then named properties must also be assignable)
}

let myDict: Dictionary = { prop1: "value1", prop2: 100, length: 2 };
console.log(myDict["prop1"]);

3.6 类类型 (Class Types) - 实现接口

接口可以强制一个类去符合某种契约。使用 implements 关键字。

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) {}
}

let clock = new Clock(10, 30);
console.log(clock.currentTime);

3.7 接口继承 (Extending Interfaces)

接口可以像类一样相互继承,允许你复用接口定义。

interface Shape {
color: string;
}

interface PenStroke {
penWidth: number;
}

interface Square extends Shape, PenStroke {
// 可以继承多个接口
sideLength: number;
}

let square = {} as Square; // 类型断言
square.color = "blue";
square.sideLength = 10;
square.penWidth = 5.0;

4. 类 (Classes)

TypeScript 支持基于类的面向对象编程,包括 ES6 中的类特性,并添加了如访问修饰符等功能。

4.1 基本定义

使用 class 关键字。

class Greeter {
greeting: string; // 属性

constructor(message: string) {
// 构造函数
this.greeting = message;
}

greet() {
// 方法
return "Hello, " + this.greeting;
}
}

let greeter = new Greeter("world");
console.log(greeter.greet());

4.2 继承 (Inheritance)

使用 extends 关键字实现继承。子类可以覆盖父类的方法,并使用 super 关键字调用父类的构造函数和方法。

class Animal {
name: string;
constructor(theName: string) {
this.name = theName;
}
move(distanceInMeters: number = 0) {
console.log(`${this.name} moved ${distanceInMeters}m.`);
}
}

class Snake extends Animal {
constructor(name: string) {
super(name);
} // 必须调用 super()
move(distanceInMeters = 5) {
// 覆盖父类方法
console.log("Slithering...");
super.move(distanceInMeters); // 调用父类方法
}
}

class Horse extends Animal {
constructor(name: string) {
super(name);
}
move(distanceInMeters = 45) {
console.log("Galloping...");
super.move(distanceInMeters);
}
}

let sam = new Snake("Sammy the Python");
let tom: Animal = new Horse("Tommy the Palomino"); // 可以用父类类型引用子类实例

sam.move();
tom.move(34);

4.3 访问修饰符 (Access Modifiers)

  • public (默认): 成员可以在任何地方被访问。
  • private: 成员只能在其声明的类内部访问。
  • protected: 成员只能在其声明的类及其子类中访问。
class Person {
public name: string; // 显式 public
private age: number;
protected gender: string;

constructor(name: string, age: number, gender: string) {
this.name = name;
this.age = age;
this.gender = gender;
}

public getAge() {
return this.age; // OK, 在类内部访问 private
}
}

class Employee extends Person {
private department: string;

constructor(name: string, age: number, gender: string, department: string) {
super(name, age, gender);
this.department = department;
}

public getInfo() {
// console.log(this.age); // Error: Property 'age' is private and only accessible within class 'Person'.
console.log(
`Name: ${this.name}, Gender: ${this.gender}, Dept: ${this.department}`
); // OK: public name, protected gender
}
}

let person = new Person("Alice", 30, "female");
console.log(person.name); // OK
// console.log(person.age); // Error: Property 'age' is private...
// console.log(person.gender); // Error: Property 'gender' is protected...
console.log(person.getAge()); // OK

let emp = new Employee("Bob", 25, "male", "Sales");
emp.getInfo();

参数属性 (Parameter Properties): 可以在构造函数参数前添加访问修饰符,自动创建同名属性并赋值。

class Octopus {
readonly numberOfLegs: number = 8;
// 将 name 声明和赋值合并到构造函数参数
constructor(readonly name: string) {}
}
let dad = new Octopus("Man with the 8 strong legs");
// dad.name = "New Name"; // Error! name is readonly.
console.log(dad.name);

4.4 只读修饰符 (Readonly Modifier)

readonly 关键字可以用在属性上,表示该属性只能在声明时或构造函数里被初始化。

class TestReadonly {
readonly value: string;

constructor(val: string) {
this.value = val; // OK in constructor
}

changeValue() {
// this.value = "new value"; // Error: Cannot assign to 'value' because it is a read-only property.
}
}

4.5 存取器 (Accessors - Getters / Setters)

允许你像访问属性一样调用函数,用于控制属性的读取和设置。

let passcode = "secret passcode";

class EmployeePass {
private _fullName: string = "";

get fullName(): string {
console.log("Getter called");
return this._fullName;
}

set fullName(newName: string) {
console.log("Setter called");
if (passcode && passcode === "secret passcode") {
this._fullName = newName;
} else {
console.log("Error: Unauthorized update of employee!");
}
}
}

let employee = new EmployeePass();
employee.fullName = "Bob Smith"; // 调用 setter
if (employee.fullName) {
// 调用 getter
console.log(employee.fullName);
}

4.6 静态属性和方法 (Static Members)

使用 static 关键字定义的属性和方法属于类本身,而不是类的实例。通过类名直接访问。

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;
}

static createDefaultGrid() {
// 静态方法
return new Grid(1.0);
}

constructor(public scale: number) {}
}

let grid1 = new Grid(1.0);
let grid2 = new Grid(5.0);

console.log(Grid.origin); // { x: 0, y: 0 } - 通过类名访问
let defaultGrid = Grid.createDefaultGrid(); // 调用静态方法
console.log(defaultGrid.calculateDistanceFromOrigin({ x: 10, y: 10 }));

4.7 抽象类 (Abstract Classes)

抽象类作为其他派生类的基类使用。它们一般不会直接被实例化。不同于接口,抽象类可以包含成员的实现细节。abstract 关键字是用于定义抽象类和在抽象类内部定义抽象方法。抽象方法必须在派生类中实现。

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...");
}
}

let department: Department; // 允许创建一个对抽象类型的引用
// department = new Department(); // Error: Cannot create an instance of an abstract class.
department = new AccountingDepartment(); // OK
department.printName();
department.printMeeting();
// department.generateReports(); // Error: Property 'generateReports' does not exist on type 'Department'. (需要类型断言或用子类类型引用)
(department as AccountingDepartment).generateReports(); // OK with type assertion

5. 函数 (Functions)

TypeScript 为 JavaScript 函数添加了类型注解和其他特性。

5.1 函数类型注解

可以为函数的参数和返回值添加类型注解。

// 命名函数
function add(x: number, y: number): number {
return x + y;
}

// 匿名函数 (函数表达式)
let myAdd = function (x: number, y: number): number {
return x + y;
};

// 也可以只注解变量类型,TS 会尝试推断
let mySubtract: (baseValue: number, increment: number) => number = function (
x,
y
) {
return x - y;
};

5.2 可选参数和默认参数

  • 可选参数: 参数名后加 ?。可选参数必须位于必选参数之后。
  • 默认参数: 参数后加 = 默认值。带默认值的参数被视为可选参数。
function buildName(firstName: string, lastName?: string): string {
// lastName is optional
if (lastName) {
return firstName + " " + lastName;
} else {
return firstName;
}
}

let result1 = buildName("Bob"); // OK, lastName is undefined
let result2 = buildName("Bob", "Adams"); // OK
// let result3 = buildName("Bob", "Adams", "Sr."); // Error: Expected 2 arguments, but got 3.

function calculatePay(rate: number, hours: number = 40): number {
// hours has default value
return rate * hours;
}

let pay1 = calculatePay(15); // Uses default hours (40) -> 600
let pay2 = calculatePay(15, 20); // Uses provided hours (20) -> 300

5.3 剩余参数 (Rest Parameters)

用于将多个参数收集到一个变量中(必须是数组类型)。剩余参数必须是参数列表中的最后一个。

function buildFullName(firstName: string, ...restOfName: string[]): string {
return firstName + " " + restOfName.join(" ");
}

let employeeName = buildFullName("Joseph", "Samuel", "Lucas", "MacKinzie");
console.log(employeeName); // "Joseph Samuel Lucas MacKinzie"

5.4 this 参数

有时需要明确指定函数中 this 的类型。this 参数是一个假的参数,它出现在参数列表的最前面。

interface Card {
suit: string;
card: number;
}
interface Deck {
suits: string[];
cards: number[];
createCardPicker(this: Deck): () => Card; // Explicitly defining 'this' type for the function
}

let deck: Deck = {
suits: ["hearts", "spades", "clubs", "diamonds"],
cards: Array(52),
// NOTE: The function now explicitly specifies that its callee must be of type Deck
createCardPicker: function (this: Deck) {
return () => {
let pickedCard = Math.floor(Math.random() * 52);
let pickedSuit = Math.floor(pickedCard / 13);

// 'this' here refers to the 'Deck' object because of the arrow function's lexical scoping
return { suit: this.suits[pickedSuit], card: pickedCard % 13 };
};
},
};

let cardPicker = deck.createCardPicker();
let pickedCard = cardPicker();

console.log("card: " + pickedCard.card + " of " + pickedCard.suit);

5.5 函数重载 (Overloads)

允许为同一个函数根据不同的参数类型或数量提供多个函数签名(类型定义),然后提供一个通用的实现。

// Overload signatures
function processInput(input: string): string;
function processInput(input: number): number;
function processInput(input: boolean): boolean;

// Implementation signature (must be compatible with all overloads)
function processInput(
input: string | number | boolean
): string | number | boolean {
if (typeof input === "string") {
return `Processed string: ${input}`;
} else if (typeof input === "number") {
return input * 2;
} else {
return !input;
}
}

let resultStr = processInput("hello"); // Type is inferred as string
let resultNum = processInput(10); // Type is inferred as number
let resultBool = processInput(true); // Type is inferred as boolean

console.log(resultStr);
console.log(resultNum);
console.log(resultBool);

// let resultErr = processInput(null); // Error: No overload matches this call.

注意: 实现签名 (function processInput(input: any)... 或使用联合类型) 本身不能被直接调用。编译器只认重载签名。

6. 泛型 (Generics)

泛型允许你编写可重用的、适用于多种类型的组件(如函数、类、接口),同时保持类型安全。

6.1 泛型函数

使用类型变量(如 <T>)来捕获用户提供的类型。

// <T> is the type parameter declaration
function identity<T>(arg: T): T {
return arg;
}

// Calling the generic function
let output1 = identity<string>("myString"); // Explicitly specify type T as string
let output2 = identity("myString"); // Type inference: T is inferred as string
let output3 = identity<number>(100); // Explicitly specify type T as number
let output4 = identity(100); // Type inference: T is inferred as number

console.log(typeof output1, output1); // string myString
console.log(typeof output4, output4); // number 100

6.2 泛型接口

接口也可以是泛型的。

interface GenericIdentityFn<T> {
(arg: T): T;
}

function identityAgain<T>(arg: T): T {
return arg;
}

let myIdentity: GenericIdentityFn<number> = identityAgain;
console.log(myIdentity(5)); // 5
// console.log(myIdentity("hello")); // Error: Argument of type 'string' is not assignable to parameter of type 'number'.

6.3 泛型类

类也可以是泛型的。

class GenericNumber<T> {
zeroValue: T | undefined;
add: ((x: T, y: T) => T) | undefined;
}

let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function (x, y) {
return x + y;
};
console.log(myGenericNumber.add(myGenericNumber.zeroValue, 10)); // 10

let stringNumeric = new GenericNumber<string>();
stringNumeric.zeroValue = "";
stringNumeric.add = function (x, y) {
return x + y;
};
console.log(stringNumeric.add(stringNumeric.zeroValue, "test")); // "test"

6.4 泛型约束 (Generic Constraints)

有时你希望限制泛型类型变量可以接受的类型范围。可以使用 extends 关键字添加约束。

interface Lengthwise {
length: number; // Constraint: T must have a 'length' property
}

// T must have a .length property
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length); // Now we know it has a .length property, so no error
return arg;
}

loggingIdentity("hello"); // OK, string has length
loggingIdentity([1, 2, 3]); // OK, array has length
loggingIdentity({ length: 10, value: 3 }); // OK, object has length property

// loggingIdentity(3); // Error: Argument of type 'number' is not assignable to parameter of type 'Lengthwise'. (number doesn't have length)
// loggingIdentity({ value: 3 }); // Error: Property 'length' is missing in type '{ value: number; }' but required in type 'Lengthwise'.

7. 类型推断 (Type Inference)

TypeScript 编译器可以在很多情况下自动推断出变量或表达式的类型,无需显式注解。

let x = 3; // TypeScript infers x as 'number'
// x = "hello"; // Error: Type 'string' is not assignable to type 'number'.

let y = [0, 1, null]; // TypeScript infers y as '(number | null)[]'

// Contextual Typing: TS infers parameter types based on context
window.onmousedown = function (mouseEvent /* Inferred as MouseEvent */) {
// console.log(mouseEvent.button); // OK
// console.log(mouseEvent.kangaroo); // Error: Property 'kangaroo' does not exist on type 'MouseEvent'.
};

// Best Common Type: TS finds the best common type for multiple expressions
let arr = [0, 1, "hello", null]; // Inferred as (string | number | null)[]

8. 类型断言 (Type Assertions)

类型断言允许你覆盖 TypeScript 的类型推断,告诉编译器“相信我,我知道这个变量的类型”。它类似于其他语言中的类型转换,但没有运行时的影响,仅用于编译时检查。有两种语法:

  1. 尖括号语法: <Type>value (在 JSX 中不能使用)
  2. as 语法: value as Type (推荐,尤其是在 React/JSX 项目中)
let someValue: any = "this is a string";

// 1. Angle-bracket syntax
let strLength1: number = (<string>someValue).length;

// 2. as-syntax (preferred)
let strLength2: number = (someValue as string).length;

console.log(strLength1, strLength2);

// Be careful: Assertions bypass checks, can lead to runtime errors if wrong
let potentiallyWrong = 123;
// console.log((potentiallyWrong as string).length); // Compiles, but throws TypeError at runtime

9. 高级类型 (Advanced Types)

9.1 联合类型 (Union Types)

表示一个值可以是几种类型之一,使用 | 分隔。

function printId(id: number | string) {
console.log("Your ID is: " + id);
// Need type guard to use type-specific methods
if (typeof id === "string") {
console.log(id.toUpperCase()); // OK, id is string here
} else {
console.log(id.toFixed(2)); // OK, id is number here
}
}

printId(101); // OK
printId("202"); // OK
// printId({ myID: 22342 }); // Error

9.2 交叉类型 (Intersection Types)

将多个类型合并为一个类型,包含所有类型的成员。使用 & 分隔。常用于混入 (mixins) 或其他不适合继承的场景。

interface Loggable {
log(message: string): void;
}
interface Serializable {
serialize(): string;
}

type LoggableSerializable = Loggable & Serializable;

class ConsoleLogger implements Loggable {
log(message: string) {
console.log(message);
}
}

class JsonSerialier implements Serializable {
constructor(private data: any) {}
serialize(): string {
return JSON.stringify(this.data);
}
}

// Helper function to mixin properties
function extend<First extends {}, Second extends {}>(
first: First,
second: Second
): First & Second {
const result: Partial<First & Second> = {};
for (const prop in first) {
if (first.hasOwnProperty(prop)) {
(result as First)[prop] = first[prop];
}
}
for (const prop in second) {
if (second.hasOwnProperty(prop)) {
(result as Second)[prop] = second[prop];
}
}
return result as First & Second;
}

class CombinedItem {
constructor(private data: any) {}

// Manually implement combined interface
log(message: string) {
console.log(`[LOG] ${message}: ${this.serialize()}`);
}
serialize(): string {
return JSON.stringify(this.data);
}
}

// Using the type
let item: LoggableSerializable = new CombinedItem({ value: 42 });
item.log("Item data");
console.log(item.serialize());

9.3 类型别名 (Type Aliases)

使用 type 关键字为类型创建一个新名字。可以用于原始类型、联合类型、交叉类型、元组或任何其他需要手写类型注解的地方。

type Point = { x: number; y: number };
type ID = string | number;
type StringOrNumberArray = (string | number)[];
type Tree<T> = {
// Type alias can also be generic
value: T;
left?: Tree<T>;
right?: Tree<T>;
};

let p: Point = { x: 10, y: 20 };
let userId: ID = "user-123";
userId = 456;

let mixedArray: StringOrNumberArray = [1, "two", 3, "four"];
let stringTree: Tree<string> = {
value: "root",
left: { value: "left-child" },
};

9.4 字面量类型 (Literal Types)

允许你指定变量必须具有的确切值。常与联合类型结合使用,构成“可辨识联合类型”。

type Easing = "ease-in" | "ease-out" | "ease-in-out"; // String literal union

let animationTiming: Easing = "ease-in"; // OK
// animationTiming = "linear"; // Error: Type '"linear"' is not assignable to type 'Easing'.

type DiceRoll = 1 | 2 | 3 | 4 | 5 | 6; // Numeric literal union
let roll: DiceRoll = 4; // OK
// roll = 7; // Error

interface Circle {
kind: "circle"; // Literal type property acts as a discriminant
radius: number;
}
interface SquareShape {
// Renamed to avoid conflict with built-in Square
kind: "square";
sideLength: number;
}
type ShapeUnion = Circle | SquareShape; // Discriminated union

function getArea(shape: ShapeUnion) {
switch (shape.kind) {
case "circle":
return Math.PI * shape.radius ** 2; // TS knows shape is Circle here
case "square":
return shape.sideLength ** 2; // TS knows shape is SquareShape here
}
}
console.log(getArea({ kind: "circle", radius: 10 }));
console.log(getArea({ kind: "square", sideLength: 5 }));

9.5 索引类型 (Index Types) 和 映射类型 (Mapped Types)

  • 索引类型查询 (keyof): 获取一个类型所有公共属性名的联合类型。
  • 索引访问类型 (T[K]): 获取类型 T 的属性 K 的类型。
  • 映射类型: 基于一个现有类型创建新类型,对原类型的每个属性应用转换。
interface UserProfile {
id: number;
name: string;
email?: string;
isAdmin: boolean;
}

// 1. keyof
type UserProfileKeys = keyof UserProfile; // "id" | "name" | "email" | "isAdmin"
let key: UserProfileKeys = "name"; // OK
// key = "age"; // Error

// 2. Indexed Access Type (Lookup Type)
type UserIdType = UserProfile["id"]; // number
type UserNameOrEmail = UserProfile["name" | "email"]; // string | string | undefined -> string | undefined

// 3. Mapped Types
// Example: Make all properties readonly
type ReadonlyUserProfile = {
readonly [P in keyof UserProfile]: UserProfile[P];
};
// Equivalent to built-in Readonly<UserProfile>

// Example: Make all properties optional
type PartialUserProfile = {
[P in keyof UserProfile]?: UserProfile[P];
};
// Equivalent to built-in Partial<UserProfile>

// Example: Create a flags object based on keys
type UserProfileFlags = {
[P in keyof UserProfile]: boolean;
};
/* Resulting type:
{
id: boolean;
name: boolean;
email?: boolean; // Optionality is preserved if source has it
isAdmin: boolean;
}
*/

let userFlags: UserProfileFlags = { id: true, name: false, isAdmin: true }; // email is optional

// More complex example: Nullable properties
type Nullable<T> = {
[P in keyof T]: T[P] | null;
};
type NullableUserProfile = Nullable<UserProfile>;
let nullableUser: NullableUserProfile = {
id: 1,
name: null,
email: "test@example.com",
isAdmin: null,
};

9.6 条件类型 (Conditional Types)

允许根据条件选择两种可能的类型之一,形式为 T extends U ? X : Y

// Example: Extract specific types from a union
type NonNullable<T> = T extends null | undefined ? never : T;

type T0 = NonNullable<string | number | undefined>; // string | number
type T1 = NonNullable<string[] | null | undefined>; // string[]

// Example: Flatten array type
type Flatten<T> = T extends Array<infer Item> ? Item : T;
// 'infer Item' declares a new generic type variable 'Item' within the 'true' branch

type Str = Flatten<string[]>; // string
type Num = Flatten<number>; // number (not an array, so returns T itself)

// Example within a generic function signature
declare function process<T extends any[] | string>(
data: T
): T extends any[] ? T[number] : T;
let item = process([1, 2, 3]); // item type is 'number' (T[number] for array)
let strItem = process("hello"); // strItem type is 'string' (T for string)

// Built-in conditional types: Exclude, Extract, NonNullable, ReturnType, InstanceType
type T2 = Exclude<"a" | "b" | "c", "a">; // "b" | "c"
type T3 = Extract<"a" | "b" | "c", "a" | "f">; // "a"
type T4 = ReturnType<() => string>; // string

9.7 工具类型 (Utility Types)

TypeScript 提供了一些内置的工具类型,用于常见的类型转换(许多基于映射类型和条件类型实现)。

  • Partial<T>: 将 T 的所有属性设为可选。
  • Required<T>: 将 T 的所有属性设为必选。
  • Readonly<T>: 将 T 的所有属性设为只读。
  • Record<K, T>: 创建一个对象类型,其属性键为 K 类型,属性值为 T 类型。
  • Pick<T, K>: 从 T 中选择一组属性 K 来构造新类型。
  • Omit<T, K>: 从 T 中排除一组属性 K 来构造新类型。
  • Exclude<T, U>: 从 T 中排除可分配给 U 的类型。
  • Extract<T, U>: 从 T 中提取可分配给 U 的类型。
  • NonNullable<T>: 从 T 中排除 nullundefined
  • Parameters<T>: 获取函数类型 T 的参数类型组成的元组。
  • ConstructorParameters<T>: 获取构造函数类型 T 的参数类型组成的元组。
  • ReturnType<T>: 获取函数类型 T 的返回值类型。
  • InstanceType<T>: 获取构造函数类型 T 的实例类型。
interface Todo {
title: string;
description: string;
completed: boolean;
createdAt: number;
}

// Partial: Make all properties optional
type PartialTodo = Partial<Todo>;
// { title?: string; description?: string; completed?: boolean; createdAt?: number; }

// Required: Make all properties required (useful if original had optionals)
// type RequiredTodo = Required<PartialTodo>; // { title: string; description: string; completed: boolean; createdAt: number; }

// Readonly: Make all properties readonly
type ReadonlyTodo = Readonly<Todo>;
// { readonly title: string; readonly description: string; readonly completed: boolean; readonly createdAt: number; }

// Record: Create a dictionary-like type
type PageInfo = { title: string; visited: boolean };
type Pages = "home" | "about" | "contact";
const navInfo: Record<Pages, PageInfo> = {
home: { title: "Home", visited: true },
about: { title: "About", visited: false },
contact: { title: "Contact", visited: false },
};

// Pick: Select specific properties
type TodoPreview = Pick<Todo, "title" | "completed">;
// { title: string; completed: boolean; }

// Omit: Remove specific properties
type TodoInfo = Omit<Todo, "completed" | "createdAt">;
// { title: string; description: string; }

// Parameters: Get function parameter types
type PointPrinterParams = Parameters<(p: { x: number; y: number }) => void>; // [(p: { x: number; y: number })]
let params: PointPrinterParams = [{ x: 1, y: 2 }];

// ReturnType: Get function return type
type GreetingReturnType = ReturnType<() => string>; // string
let greetingResult: GreetingReturnType = "hello";

10. 模块 (Modules)

TypeScript 沿用了 ES6 (ES2015) 的模块系统。任何包含顶级 importexport 的文件都被视为一个模块。

10.1 导出 (Export)

使用 export 关键字导出变量、函数、类、接口、类型别名等。

// --- StringValidator.ts ---
export interface StringValidator {
isAcceptable(s: string): boolean;
}

export const lettersRegexp = /^[A-Za-z]+$/;

export class LettersOnlyValidator implements StringValidator {
isAcceptable(s: string) {
return lettersRegexp.test(s);
}
}

// Export statement
const numberRegexp = /^[0-9]+$/;
export { numberRegexp };

// Re-export
// export { ZipCodeValidator as RegExpBasedZipCodeValidator } from "./ZipCodeValidator";

// Default export (only one per module)
export default class DefaultValidator implements StringValidator {
isAcceptable(s: string): boolean {
return s.length > 0;
}
}

10.2 导入 (Import)

使用 import 关键字导入其他模块导出的内容。

// --- TestValidators.ts ---
// Import specific named exports
import {
LettersOnlyValidator,
lettersRegexp as letters,
} from "./StringValidator";

// Import using namespace
import * as validator from "./StringValidator";

// Import default export
import DefaultValidator from "./StringValidator";

let myString = "Hello";

let lettersValidator = new LettersOnlyValidator();
console.log(`LettersOnlyValidator: ${lettersValidator.isAcceptable(myString)}`); // true

let numberValidator = new validator.LettersOnlyValidator(); // Using namespace import
console.log(
`Namespace Import - LettersOnly: ${numberValidator.isAcceptable("12345")}`
); // false
console.log(`Namespace Import - Regexp: ${validator.numberRegexp.test("123")}`); // true

console.log(`Imported regexp: ${letters.test(myString)}`); // true

let defaultVal = new DefaultValidator();
console.log(`Default Validator: ${defaultVal.isAcceptable("")}`); // false

11. 命名空间 (Namespaces)

在 ES6 模块成为标准之前,TypeScript 使用 namespace (早期版本叫 internal module) 来组织代码,避免全局命名冲突。虽然现在推荐使用 ES6 模块,但在某些场景(如组织大型项目内部结构或编写 .d.ts 文件)仍可能用到。

namespace Validation {
export interface StringValidator {
isAcceptable(s: string): boolean;
}

const lettersRegexp = /^[A-Za-z]+$/;

export class LettersOnlyValidator implements StringValidator {
isAcceptable(s: string) {
return lettersRegexp.test(s);
}
}

export class ZipCodeValidator implements StringValidator {
isAcceptable(s: string) {
return s.length === 5 && lettersRegexp.test(s); // Mistake here, should be number regexp
}
}
}

// Usage
let validators: { [s: string]: Validation.StringValidator } = {};
validators["ZIP code"] = new Validation.ZipCodeValidator();
validators["Letters only"] = new Validation.LettersOnlyValidator();

console.log(validators["Letters only"].isAcceptable("HelloWorld")); // true

12. 声明文件 (.d.ts)

当你想在 TypeScript 项目中使用纯 JavaScript 库时,需要声明文件 (.d.ts) 来告诉 TypeScript 编译器该库的类型信息(变量、函数、类等)。

12.1 使用已有的声明文件

可以通过 npm 安装社区维护的类型声明包,通常以 @types/ 开头。

npm install --save-dev @types/lodash

然后在 TypeScript 代码中直接 import 和使用库,就像它是用 TypeScript 写的一样。

import _ from "lodash";

console.log(_.shuffle([1, 2, 3, 4]));

12.2 编写自己的声明文件

如果某个库没有类型声明,或者你想为自己的 JavaScript 代码提供类型信息,可以手动编写 .d.ts 文件。

// --- my-library.js --- (A simple JavaScript library)
/*
module.exports = {
greet: function(name) {
return "Hello, " + name;
},
version: "1.0.0"
};
*/

// --- my-library.d.ts --- (Declaration file for the library)
declare module "my-library" {
// Declare the module name
export function greet(name: string): string; // Declare exported function signature
export const version: string; // Declare exported constant type
}

// --- main.ts --- (Using the library with declarations)
import { greet, version } from "my-library";

console.log(greet("TypeScript")); // OK, type checking works
console.log("Library version:", version); // OK
// greet(123); // Error: Argument of type 'number' is not assignable to parameter of type 'string'.

13. tsconfig.json 配置文件

tsconfig.json 文件位于项目的根目录,用于指定 TypeScript 编译器的选项和项目文件。

13.1 主要配置项

  • compilerOptions: 包含各种编译选项。
    • target: 指定编译后的 ECMAScript 目标版本 (e.g., "es5", "es2016", "esnext").
    • module: 指定模块系统 (e.g., "commonjs", "es2015", "esnext").
    • outDir: 指定编译后 .js 文件的输出目录。
    • rootDir: 指定输入 .ts 文件根目录(用于控制输出结构)。
    • strict: 启用所有严格类型检查选项 (推荐)。包括 noImplicitAny, strictNullChecks, strictFunctionTypes, strictBindCallApply, strictPropertyInitialization, noImplicitThis, alwaysStrict
    • esModuleInterop: 允许通过 import x from 'module' 的方式导入 CommonJS 模块 (如果模块没有 export default)。
    • declaration: 生成相应的 .d.ts 声明文件。
    • sourceMap: 生成 .map 文件,用于调试。
    • lib: 指定编译时需要包含的库文件 (e.g., ["dom", "es2015"]).
    • jsx: 配置 JSX 支持 ("preserve", "react", "react-native").
  • include: 指定需要编译的文件或目录模式 (e.g., ["src/**/*"]).
  • exclude: 指定不需要编译的文件或目录模式 (e.g., ["node_modules", "**/*.spec.ts"]).
  • files: 指定一个明确的文件列表进行编译。

13.2 示例 tsconfig.json

{
"compilerOptions": {
/* Basic Options */
"target": "es2016", // Specify ECMAScript target version
"module": "commonjs", // Specify module code generation
"lib": ["es2016", "dom"], // Specify library files to be included
"outDir": "./dist", // Redirect output structure to the directory
"rootDir": "./src", // Specify the root directory of input files

/* Strict Type-Checking Options */
"strict": true, // Enable all strict type-checking options
// "noImplicitAny": true, // Raise error on expressions and declarations with an implied 'any' type
// "strictNullChecks": true, // Enable strict null checks
// "strictFunctionTypes": true, // Enable strict checking of function types
// "strictPropertyInitialization": true, // Enable strict checking for property initialization in classes
// "noImplicitThis": true, // Raise error on 'this' expressions with an implied 'any' type
// "alwaysStrict": true, // Parse in strict mode and emit "use strict" for each source file

/* Module Resolution Options */
"moduleResolution": "node", // Specify module resolution strategy
"baseUrl": "./", // Base directory to resolve non-absolute module names
"paths": {
// Series of entries which re-map imports to lookup locations
"@/*": ["src/*"]
},
"esModuleInterop": true, // Enables emit interoperability between CommonJS and ES Modules
"allowSyntheticDefaultImports": true, // Allow default imports from modules with no default export

/* Source Map Options */
"sourceMap": true, // Generates corresponding '.map' file

/* Advanced Options */
"forceConsistentCasingInFileNames": true, // Disallow inconsistently-cased references to the same file.
"skipLibCheck": true // Skip type checking of all declaration files (*.d.ts).
},
"include": [
"src/**/*" // Include all files in the src directory
],
"exclude": [
"node_modules", // Exclude node_modules
"**/*.spec.ts", // Exclude test files
"dist" // Exclude output directory
]
}