Skip to content

TypeScript 类型系统全面指南:从基础到高级应用

一、TypeScript 基础类型详解

1.1 原始数据类型

1.1.1 数字类型(number)

在 TypeScript 中,number 类型表示所有数字,包括整数和浮点数,没有专门的整数类型。这与 JavaScript 中的数字类型一致。

基本使用:

typescript
let age: number = 30;
let temperature: number = 36.5;
let binaryNumber: number = 0b1010; // 二进制数10
let octalNumber: number = 0o744; // 八进制数484
let hexNumber: number = 0xf00d; // 十六进制数61453
let bigNumber: bigint = 100n; // 大整数类型

注意:TypeScript 和 JavaScript 一样,不区分整数和浮点数,统一使用 number 类型表示所有数值。

1.1.2 字符串类型(string)

string 类型用于表示文本数据,可以使用单引号、双引号或反引号(模板字符串)定义。

基本使用:

typescript
let name: string = "Alice";
let greeting: string = "Hello, TypeScript!";
let multiLine: string = `这是一个
多行字符串`;

模板字符串: TypeScript 支持模板字符串,允许在字符串中嵌入表达式,使用${}语法:

typescript
let name: string = "Alice";
let age: number = 30;
let message: string = `我的名字是${name},今年${age + 1}岁。`;
console.log(message); // 输出:我的名字是Alice,今年31岁。

1.1.3 布尔类型(boolean)

boolean 类型表示逻辑值,只有两个可能的值:true 和 false。

基本使用:

typescript
let isDone: boolean = false;
let hasPermission: boolean = true;

1.1.4 null 和 undefined

null 和 undefined 在 TypeScript 中分别表示空值和未定义值。

基本使用:

typescript
let empty: null = null;
let uninitialized: undefined = undefined;

严格空值检查: 在 TypeScript 中启用严格空值检查(--strictNullChecks)后,null 和 undefined 只能赋值给它们各自的类型或 void 类型:

typescript
// 启用严格空值检查
let x: number;
x = 1; // 正确
x = null; // 错误:不能将null赋值给number类型
x = undefined; // 错误:不能将undefined赋值给number类型

如果需要允许变量接受 null 或 undefined,可以使用联合类型:

typescript
let maybeNumber: number | null | undefined;
maybeNumber = 1; // 正确
maybeNumber = null; // 正确
maybeNumber = undefined; // 正确

1.1.5 void 类型

void 类型表示没有值,通常用于函数返回值类型,表示该函数不返回任何值。

基本使用:

typescript
function logMessage(message: string): void {
  console.log(message);
}

声明一个 void 类型的变量只能赋值 undefined 或 null(在非严格模式下):

typescript
let unusable: void = undefined;
// 在严格模式下,void类型变量只能赋值undefined
// 在非严格模式下,可以赋值null

1.1.6 never 类型

never 类型表示永远不会出现的值,通常用于函数返回值类型,表示该函数永远不会正常返回(例如抛出异常或无限循环)。

基本使用:

typescript
function throwError(message: string): never {
  throw new Error(message);
}

function infiniteLoop(): never {
  while (true) {}
}

never 类型是所有类型的子类型,可以赋值给任何类型,但没有类型可以赋值给 never 类型(除了 never 本身):

typescript
let x: never;
let y: number;
x = 123; // 错误:不能将number类型赋值给never类型
y = throwError("出错了"); // 正确:never类型可以赋值给number类型

1.2 复合类型

1.2.1 数组类型

数组类型表示相同类型元素的有序集合。

基本语法:

typescript
// 元素类型后面加方括号
let numbers: number[] = [1, 2, 3];

// 使用泛型Array类型
let names: Array<string> = ["Alice", "Bob"];

多维数组:

typescript
let matrix: number[][] = [
  [1, 2],
  [3, 4],
];

1.2.2 元组类型(tuple)

元组类型是 TypeScript 特有的类型,用于表示已知元素数量和类型的数组,每个元素可以是不同的类型。

基本使用:

typescript
let person: [string, number] = ["Alice", 30];

可以通过索引访问元组元素,但必须符合定义的类型:

typescript
let name: string = person[0]; // 正确
let age: number = person[1]; // 正确
// person[2] = "test"; // 错误:索引2超出元组长度

也可以解构元组:

typescript
let [username, userAge] = person;
console.log(username); // Alice
console.log(userAge); // 30

1.2.3 对象类型

对象类型用于描述具有特定属性和方法的对象结构。

基本语法:

typescript
let person: { name: string; age: number } = {
  name: "Alice",
  age: 30,
};

也可以使用接口定义对象类型:

typescript
interface Person {
  name: string;
  age: number;
}

let person: Person = {
  name: "Alice",
  age: 30,
};

可选属性: 使用问号?表示可选属性:

typescript
interface Person {
  name: string;
  age?: number;
}

let person: Person = {
  name: "Alice",
  // age属性可选,可以省略
};

只读属性: 使用 readonly 关键字表示只读属性,只能在对象初始化时赋值:

typescript
interface Person {
  readonly id: number;
  name: string;
}

let person: Person = {
  id: 1,
  name: "Alice",
};

// person.id = 2; // 错误:不能修改只读属性

1.2.4 枚举类型(enum)

枚举类型用于定义一组命名常量,使代码更易读和维护。

基本使用:

typescript
enum Color {
  Red,
  Green,
  Blue,
}

let favoriteColor: Color = Color.Green;
console.log(favoriteColor); // 输出1(默认从0开始编号)

手动赋值: 可以手动为枚举成员赋值:

typescript
enum Color {
  Red = 1,
  Green,
  Blue,
}

let favoriteColor: Color = Color.Green;
console.log(favoriteColor); // 输出2(自动递增)

也可以为每个成员显式赋值:

typescript
enum Color {
  Red = 1,
  Green = 2,
  Blue = 4,
}

字符串枚举: 枚举成员也可以是字符串类型:

typescript
enum Direction {
  Up = "UP",
  Down = "DOWN",
  Left = "LEFT",
  Right = "RIGHT",
}

1.3 特殊类型

1.3.1 any 类型

any 类型表示可以是任意类型的值,绕过 TypeScript 的类型检查。

基本使用:

typescript
let value: any = 42;
value = "hello"; // 允许,any类型可以动态改变类型
value.foo(); // 允许,即使foo属性不存在

使用场景:

  • 变量值会动态改变时(如用户输入)
  • 改写现有代码时,暂时绕过类型检查
  • 定义存储各种类型数据的数组时

注意:过度使用 any 类型会降低 TypeScript 的类型安全优势,应谨慎使用。

1.3.2 unknown 类型

unknown 类型与 any 类似,但更安全,必须经过类型检查后才能赋值给其他类型变量。

基本使用:

typescript
let value: unknown = 42;
value = "hello"; // 允许

let num: number;
num = value; // 错误:不能将unknown类型直接赋值给number类型

if (typeof value === "number") {
  num = value; // 正确:经过类型检查后可以赋值
}

类型检查: 使用类型断言或类型保护来检查 unknown 类型的值:

typescript
function getSafeValue(value: unknown): string {
  if (typeof value === "string") {
    return value;
  }
  throw new Error("值不是字符串类型");
}

1.3.3 字面量类型

字面量类型允许变量只能取特定的字面量值。

基本使用:

typescript
let direction: "up" | "down" | "left" | "right" = "up";
direction = "down"; // 正确
// direction = "middle"; // 错误:middle不在允许的字面量集合中

与联合类型结合:

typescript
type Booleanish = true | false | "yes" | "no";
let flag: Booleanish = "yes";

1.3.4 类型断言

类型断言用于告诉编译器变量的实际类型,当编译器无法自动推断类型时使用。

基本语法:

typescript
// 使用as语法
let someValue: unknown = "this is a string";
let strLength: number = (someValue as string).length;

// 使用尖括号语法(在JSX中不可用)
let strLength: number = (<string>someValue).length;

注意:类型断言只是告诉编译器变量的类型,不会实际检查值的类型,过度使用可能导致运行时错误。

二、TypeScript 高级类型

2.1 联合类型与交叉类型

2.1.1 联合类型(Union Types)

联合类型表示一个值可以是多种类型之一,使用|符号分隔。

基本使用:

typescript
let id: string | number;
id = "123"; // 正确
id = 456; // 正确

访问联合类型的属性: 只能访问联合类型中所有类型共有的属性:

typescript
function printId(id: string | number) {
  // console.log(id.length); // 错误:number类型没有length属性
  console.log(id.toString()); // 正确:所有类型都有toString方法
}

类型保护: 使用类型保护来缩小联合类型的范围:

typescript
function printId(id: string | number) {
  if (typeof id === "string") {
    console.log(id.length); // 正确:此时id被类型保护为string类型
  } else {
    console.log(id); // 正确:此时id被类型保护为number类型
  }
}

2.1.2 交叉类型(Intersection Types)

交叉类型表示一个值同时具有多种类型的特性,使用&符号分隔。

基本使用:

typescript
interface Person {
  name: string;
}

interface Employee {
  company: string;
}

type Manager = Person & Employee;

let manager: Manager = {
  name: "Alice",
  company: "ABC Corp",
};

应用场景:

  • 组合多个接口的特性
  • 扩展现有类型
  • 创建混合类型

2.2 类型别名与接口

2.2.1 类型别名(Type Aliases)

类型别名用于为现有类型创建一个新名字,增加代码可读性。

基本语法:

typescript
type Name = string;
type Age = number;
type UserId = string | number;

type Point = { x: number; y: number };

区别于接口: 类型别名可以用于原始类型、联合类型、元组等,而接口只能描述对象结构。

2.2.2 接口(Interfaces)

接口用于定义对象的结构,描述对象的属性和方法。

基本语法:

typescript
interface Person {
  name: string;
  age: number;
  sayHello(): void;
}

let person: Person = {
  name: "Alice",
  age: 30,
  sayHello() {
    console.log("Hello!");
  },
};

可选属性: 使用问号?表示可选属性:

typescript
interface Person {
  name: string;
  age?: number;
}

只读属性: 使用 readonly 关键字表示只读属性:

typescript
interface Person {
  readonly id: number;
  name: string;
}

函数类型: 接口可以描述函数类型:

typescript
interface Adder {
  (a: number, b: number): number;
}

let add: Adder = function (a, b) {
  return a + b;
};

2.2.3 类型别名与接口的区别

特性类型别名接口
定义方式type 关键字interface 关键字
可扩展性不可扩展(但可以重新定义)可扩展(使用 extends)
适用范围所有类型(原始类型、联合类型、元组等)仅对象结构
重复定义会合并(如果结构兼容)会合并(自动合并同名接口)

何时使用:

  • 描述对象结构时优先使用接口
  • 需要定义联合类型、元组或其他复合类型时使用类型别名

2.3 泛型

2.3.1 泛型基础

泛型是 TypeScript 中最强大的特性之一,允许创建可以适用于多种类型的组件,提高代码复用性和类型安全性。

基本语法:

typescript
// 泛型函数
function identity<T>(arg: T): T {
  return arg;
}

// 使用泛型函数
let output1 = identity<string>("myString"); // 显式指定类型参数
let output2 = identity(5); // 类型推断自动推断类型为number

泛型接口:

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

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

let myIdentity: GenericIdentityFn<number> = identity;

泛型类:

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

2.3.2 泛型约束

泛型约束用于限制类型变量的类型范围,而不是接受任何可能的类型。

基本语法:

typescript
interface Lengthwise {
  length: number;
}

function loggingIdentity<T extends Lengthwise>(arg: T): T {
  console.log(arg.length); // 现在可以安全访问length属性
  return arg;
}

loggingIdentity("hello"); // 正确:string有length属性
// loggingIdentity(5); // 错误:number没有length属性

多个类型参数约束:

typescript
function copyFields<T extends U, U>(target: T, source: U): T {
  for (let id in source) {
    target[id] = source[id] as any;
  }
  return target;
}

let x = { a: 1, b: 2, c: 3, d: 4 };
copyFields(x, { b: 10, d: 20 }); // x将变为{ a:1, b:10, c:3, d:20 }

2.3.3 泛型高级应用

泛型工具类型: TypeScript 提供了多个内置泛型工具类型,如 Partial、Readonly、Pick 等:

typescript
// Partial<T>:将T的所有属性变为可选
type PartialUser = Partial<User>;

// Readonly<T>:将T的所有属性变为只读
type ReadonlyUser = Readonly<User>;

// Pick<T, K>:从T中选取属性K组成新类型
type NameOnlyUser = Pick<User, "name">;

泛型条件类型:

typescript
type TypeName<T> = T extends string
  ? "string"
  : T extends number
  ? "number"
  : T extends boolean
  ? "boolean"
  : T extends undefined
  ? "undefined"
  : T extends Function
  ? "function"
  : "object";

type T1 = TypeName<string>; // "string"
type T2 = TypeName<"a">; // "string"
type T3 = TypeName<() => void>; // "function"
type T4 = TypeName<string[]>; // "object"

2.4 条件类型

2.4.1 基本条件类型

条件类型允许在类型级别进行条件判断,语法类似于 JavaScript 的三元运算符。

基本语法:

typescript
T extends U ? X : Y

当 T 可以赋值给 U 时,结果为 X 类型;否则为 Y 类型。

示例:

typescript
interface IdLabel {
  id: number;
}

interface NameLabel {
  name: string;
}

type NameOrId<T extends number | string> = T extends number
  ? IdLabel
  : NameLabel;

function createLabel<T extends number | string>(idOrName: T): NameOrId<T> {
  throw "未实现";
}

let a = createLabel("typescript"); // a的类型是NameLabel
let b = createLabel(2.8); // b的类型是IdLabel
let c = createLabel(Math.random() ? "hello" : 42); // c的类型是NameLabel | IdLabel

2.4.2 infer 关键字

infer 关键字用于在条件类型中提取类型的某一部分信息。

基本语法:

typescript
T extends (...args: any) => infer R ? R : any

示例:

typescript
// 获取函数返回值类型
type ReturnType<T extends (...args: any) => any> = T extends (
  ...args: any
) => infer R
  ? R
  : any;

type Func = (a: string, b: number) => boolean;
type Result = ReturnType<Func>; // boolean

// 获取函数参数类型
type Parameters<T extends (...args: any) => any> = T extends (
  ...args: infer P
) => any
  ? P
  : never;

type FuncParams = Parameters<Func>; // [string, number]

// 提取数组元素类型
type ElementType<T> = T extends (infer U)[] ? U : T;

type StrArr = ElementType<string[]>; // string
type NumOrStrArr = ElementType<string[] | number[]>; // string | number

2.4.3 分布式条件类型

当条件类型作用于泛型类型且给定联合类型时,会自动对联合类型的每个成员应用条件类型,这种现象称为分布式条件类型。

示例:

typescript
type ToArray<Type> = Type extends any ? Type[] : never;

type StrArrOrNumArr = ToArray<string | number>;
// 等同于 string[] | number[]

阻止分发: 在某些情况下,我们可能希望阻止条件类型的自动分发,可以通过以下方法实现:

typescript
// 元组包装法
type ToArrayNonDist<Type> = [Type] extends [any] ? Type[] : never;

// 函数包装法
type ToArrayNonDist<Type> = ((arg: Type) => void) extends (arg: any) => void
  ? Type[]
  : never;

// 交叉类型{}法
type ToArrayNonDist<Type> = Type & {} extends any ? Type[] : never;

2.5 映射类型

2.5.1 基本映射类型

映射类型允许基于现有类型创建新类型,通过遍历现有类型的键并对每个键应用转换。

基本语法:

typescript
{ [Property in keyof Type]: NewType }

示例:

typescript
type OptionsFlags<Type> = {
  [Property in keyof Type]: boolean;
};

type Features = {
  darkMode: () => void;
  newUserProfile: () => void;
};

type FeatureOptions = OptionsFlags<Features>;
// 结果为:
// type FeatureOptions = {
//   darkMode: boolean;
//   newUserProfile: boolean;
// }

2.5.2 映射修饰符

映射类型支持添加或移除属性的 readonly 和?修饰符。

添加修饰符: 使用+前缀(可以省略)添加修饰符:

typescript
type CreateMutable<Type> = {
  +readonly [Property in keyof Type]: Type[Property];
};

移除修饰符: 使用-前缀移除修饰符:

typescript
type CreateMutable<Type> = {
  -readonly [Property in keyof Type]: Type[Property];
};

type LockedAccount = {
  readonly id: string;
  readonly name: string;
};

type UnlockedAccount = CreateMutable<LockedAccount>;
// 结果为:
// type UnlockedAccount = {
//   id: string;
//   name: string;
// }

移除可选属性:

typescript
type Concrete<Type> = {
  [Property in keyof Type]-?: Type[Property];
};

type MaybeUser = {
  id: string;
  name?: string;
  age?: number;
};

type User = Concrete<MaybeUser>;
// 结果为:
// type User = {
//   id: string;
//   name: string;
//   age: number;
// }

2.5.3 键重映射

TypeScript 4.1 及以上版本支持在映射类型中使用 as 子句进行键重映射。

基本语法:

typescript
{ [Property in keyof Type as NewKey]: NewType }

示例:

typescript
type Getters<Type> = {
  [Property in keyof Type as `get${Capitalize<
    string & Property
  >}`]: () => Type[Property];
};

interface Person {
  name: string;
  age: number;
  location: string;
}

type LazyPerson = Getters<Person>;
// 结果为:
// type LazyPerson = {
//   getName: () => string;
//   getAge: () => number;
//   getLocation: () => string;
// }

2.5.4 属性过滤

映射类型可以结合条件类型过滤特定属性。

示例:

typescript
type Filter<T> = {
  [K in keyof T as T[K] extends string ? K : never]: string;
};

type User = { name: string; age: number; email: string };
type FilteredUser = Filter<User>; // { name: string; email: string }

2.5.5 内置映射工具类型

TypeScript 提供了多个内置的映射工具类型,方便进行常见的类型转换:

工具类型描述
Partial<T>使 T 的所有属性变为可选
Required<T> 移除 T 的所有可选属性
Readonly<T>使 T 的所有属性变为只读
Pick<T, K>从 T 中选取属性 K 组成新类型
Omit<T, K>从 T 中排除属性 K 组成新类型
Record<K, T>创建以 K 为键、T 为值的对象类型
Exclude<T, U>从 T 中排除 U 中存在的类型
Extract<T, U>提取 T 中 U 中存在的类型
NonNullable<T>从 T 中排除 null 和 undefined
ReturnType<T>获取函数 T 的返回值类型
InstanceType<T>获取构造函数 T 的实例类型

三、函数类型与参数类型

3.1 函数类型定义

3.1.1 函数声明与表达式

函数声明:

typescript
function add(a: number, b: number): number {
  return a + b;
}

函数表达式:

typescript
let add: (a: number, b: number) => number = function (a, b) {
  return a + b;
};

箭头函数:

typescript
let add: (a: number, b: number) => number = (a, b) => a + b;

3.1.2 函数类型接口

使用接口定义函数类型:

typescript
interface Adder {
  (a: number, b: number): number;
}

let add: Adder = function (a, b) {
  return a + b;
};

3.1.3 函数返回值类型

无返回值函数:

typescript
function log(message: string): void {
  console.log(message);
}

返回对象类型:

typescript
function createUser(name: string, age: number): { name: string; age: number } {
  return { name, age };
}

返回联合类型:

typescript
function getValue(key: string): string | number | undefined {
  // 根据key返回不同类型的值
}

3.2 函数参数类型

3.2.1 必选参数

基本语法:

typescript
function greet(name: string) {
  console.log(`Hello, ${name}`);
}

greet("Alice"); // 正确
// greet(); // 错误:缺少必选参数name

3.2.2 可选参数

使用问号?表示可选参数:

typescript
function greet(name: string, message?: string) {
  console.log(message ? `${message}, ${name}` : `Hello, ${name}`);
}

greet("Alice"); // 正确
greet("Alice", "Hi"); // 正确

3.2.3 默认参数

为参数提供默认值:

typescript
function greet(name: string, message: string = "Hello") {
  console.log(`${message}, ${name}`);
}

greet("Alice"); // 正确,使用默认message
greet("Alice", "Hi"); // 正确,覆盖默认message

3.2.4 剩余参数

使用...语法表示剩余参数:

typescript
function sum(...numbers: number[]): number {
  return numbers.reduce((acc, curr) => acc + curr, 0);
}

sum(1, 2, 3); // 正确,参数为[1, 2, 3]

剩余参数的类型是数组类型,必须是函数参数列表中的最后一个参数。

3.2.5 参数解构

使用对象解构和数组解构来定义函数参数:

对象解构:

typescript
function printUser({ name, age }: { name: string; age: number }) {
  console.log(`Name: ${name}, Age: ${age}`);
}

printUser({ name: "Alice", age: 30 });

数组解构:

typescript
function printNumbers([first, second]: [number, number]) {
  console.log(`First: ${first}, Second: ${second}`);
}

printNumbers([1, 2]);

3.2.6 参数类型约束

使用泛型约束来限制函数参数的类型:

typescript
function logIdentity<T extends { length: number }>(arg: T): T {
  console.log(arg.length); // 安全访问length属性
  return arg;
}

logIdentity("hello"); // 正确
logIdentity([1, 2, 3]); // 正确
// logIdentity(123); // 错误:number类型没有length属性

3.3 函数重载

3.3.1 函数重载声明

函数重载允许定义多个同名函数,但参数列表不同。

基本语法:

typescript
// 重载声明
function add(a: number, b: number): number;
function add(a: string, b: string): string;

// 实现
function add(a: any, b: any): any {
  return a + b;
}

// 使用
let numResult = add(1, 2); // 类型为number
let strResult = add("Hello, ", "world!"); // 类型为string

重载解析: TypeScript 会根据调用时的参数类型选择最合适的重载:

typescript
// 重载声明
function makeDate(timestamp: number): Date;
function makeDate(m: number, d: number, y: number): Date;

// 实现
function makeDate(mOrTimestamp: any, d?: any, y?: any): Date {
  if (typeof mOrTimestamp === "number") {
    return new Date(mOrTimestamp);
  } else {
    return new Date(y, mOrTimestamp - 1, d);
  }
}

// 使用
let d1 = makeDate(1234567890); // 正确:调用第一个重载
let d2 = makeDate(5, 5, 2020); // 正确:调用第二个重载

3.3.2 重载与泛型的选择

在某些情况下,泛型可能比重载更简洁:

使用重载:

typescript
function identity(arg: number): number;
function identity(arg: string): string;
function identity(arg: any): any {
  return arg;
}

使用泛型:

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

何时选择:

  • 当需要不同的返回类型基于参数类型时使用重载
  • 当函数逻辑对所有类型都相同时使用泛型

四、类与接口类型

4.1 类的类型定义

4.1.1 类的基本类型定义

类的类型定义包括实例属性、实例方法、构造函数和静态成员。

基本语法:

typescript
class Person {
  // 实例属性
  name: string;
  age: number;

  // 构造函数
  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }

  // 实例方法
  greet(): void {
    console.log(
      `Hello, my name is ${this.name} and I'm ${this.age} years old.`
    );
  }
}

// 创建实例
let person: Person = new Person("Alice", 30);
person.greet(); // 输出:Hello, my name is Alice and I'm 30 years old.

4.1.2 类的继承与类型

使用 extends 关键字实现类的继承:

typescript
class Employee extends Person {
  company: string;

  constructor(name: string, age: number, company: string) {
    super(name, age); // 调用父类构造函数
    this.company = company;
  }

  // 重写父类方法
  greet(): void {
    super.greet(); // 调用父类方法
    console.log(`I work at ${this.company}.`);
  }
}

let employee: Employee = new Employee("Bob", 35, "ABC Corp");
employee.greet();

类型兼容性: 子类实例可以赋值给父类类型变量:

typescript
let person: Person = new Employee("Bob", 35, "ABC Corp");

4.1.3 类的访问修饰符

TypeScript 支持三种访问修饰符:

修饰符描述
public公共(默认),可以在任何地方访问
private私有,只能在类内部访问
protected受保护,只能在类内部及其子类中访问

使用示例:

typescript
class Person {
  public name: string;
  private secret: string;
  protected age: number;

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

  protected greet(): void {
    console.log(`Hello, my name is ${this.name}.`);
  }
}

class Employee extends Person {
  constructor(name: string, age: number) {
    super(name, age);
    // this.secret = "new secret"; // 错误:不能访问私有属性
    this.age = 30; // 正确:可以访问受保护属性
  }

  public introduce(): void {
    this.greet(); // 正确:可以调用受保护方法
  }
}

4.1.4 类的静态成员

静态成员属于类本身,而不是类的实例,可以通过类名直接访问。

使用示例:

typescript
class MathUtils {
  static PI: number = 3.1415926;

  static calculateCircleArea(radius: number): number {
    return this.PI * radius * radius;
  }
}

console.log(MathUtils.PI); // 正确:直接通过类名访问静态属性
console.log(MathUtils.calculateCircleArea(5)); // 正确:调用静态方法

4.2 类与接口的关系

4.2.1 类实现接口

类可以实现一个或多个接口,确保类符合接口定义的结构。

基本语法:

typescript
interface Drawable {
  draw(): void;
}

class Circle implements Drawable {
  radius: number;

  constructor(radius: number) {
    this.radius = radius;
  }

  draw(): void {
    console.log(`Drawing a circle with radius ${this.radius}`);
  }
}

let shape: Drawable = new Circle(5);
shape.draw(); // 输出:Drawing a circle with radius 5

实现多个接口:

typescript
interface Printable {
  print(): void;
}

class Circle implements Drawable, Printable {
  // 实现两个接口的方法
}

4.2.2 接口继承类

接口可以继承类,继承类的成员(包括私有和受保护成员)。

示例:

typescript
class Control {
  private state: any;
}

interface SelectableControl extends Control {
  select(): void;
}

class Button extends Control implements SelectableControl {
  select(): void {
    // 可以访问父类的私有成员state吗?
    // 不可以,因为SelectableControl接口继承自Control,但实现类Button无法访问Control的私有成员
  }
}

注意:当接口继承类时,接口会继承类的成员,但类的私有和受保护成员会被视为接口的私有和受保护成员,这可能导致实现类无法满足接口的要求。

4.2.3 抽象类

抽象类是不能直接实例化的类,通常作为其他类的基类,包含抽象方法和具体方法。

基本语法:

typescript
abstract class Animal {
  abstract makeSound(): void; // 抽象方法,必须在子类中实现

  move(): void {
    // 具体方法
    console.log("Moving along!");
  }
}

class Dog extends Animal {
  makeSound(): void {
    console.log("Woof!");
  }
}

// let animal: Animal = new Animal(); // 错误:不能实例化抽象类
let dog: Animal = new Dog();
dog.makeSound(); // 正确:调用子类实现的方法
dog.move(); // 正确:调用父类的具体方法

4.3 类属性类型

4.3.1 实例属性类型

实例属性属于类的每个实例,可以是任何类型。

基本语法:

typescript
class Person {
  name: string;
  age: number;
  hobbies?: string[]; // 可选属性

  constructor(name: string, age: number, hobbies?: string[]) {
    this.name = name;
    this.age = age;
    this.hobbies = hobbies;
  }
}

类型修饰符: 可以使用 readonly 修饰符将属性声明为只读:

typescript
class Person {
  readonly id: number;
  name: string;

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

let person = new Person(1, "Alice");
// person.id = 2; // 错误:不能修改只读属性

4.3.2 静态属性类型

静态属性属于类本身,而不是实例,可以通过类名直接访问。

基本语法:

typescript
class MathUtils {
  static PI: number = 3.1415926;
  static version: string = "1.0.0";
}

console.log(MathUtils.PI); // 正确:直接通过类名访问静态属性

4.3.3 索引签名

索引签名允许类的实例像数组或对象一样通过索引访问。

基本语法:

typescript
class Dictionary {
  [key: string]: any; // 字符串索引签名
}

let dict = new Dictionary();
dict["name"] = "Alice";
dict["age"] = 30;
console.log(dict["name"]); // 输出:Alice

类型限制:

typescript
class NumberDictionary {
  [key: string]: number;

  constructor(public name: string, public age: number) {}
}

let dict = new NumberDictionary("Alice", 30);
dict["score"] = 90; // 正确
// dict["name"] = "Bob"; // 错误:name属性是字符串类型,与索引签名的number类型冲突

4.3.4 类的类型别名

可以使用类型别名来简化类的类型定义:

typescript
type PersonType = typeof Person;

function createInstance<T extends { new (...args: any[]): any }>(
  ctor: T,
  ...args: any[]
): InstanceType<T> {
  return new ctor(...args);
}

let person: Person = createInstance(Person, "Alice", 30);

五、应用场景与最佳实践

5.1 函数参数类型应用场景

5.1.1 函数参数类型的最佳实践

明确参数类型:

typescript
// 好的实践:明确参数类型和返回值类型
function add(a: number, b: number): number {
  return a + b;
}

// 不好的实践:使用any类型,失去类型安全
function add(a: any, b: any): any {
  return a + b;
}

避免过度指定类型:

typescript
// 好的实践:使用更通用的类型
function print(value: any) {
  console.log(value);
}

// 不好的实践:过度指定类型,限制使用场景
function print(value: string | number) {
  console.log(value);
}

使用联合类型处理多种情况:

typescript
function format(value: string | number): string {
  if (typeof value === "string") {
    return `"${value}"`;
  } else {
    return value.toString();
  }
}

使用可选参数和默认参数:

typescript
// 可选参数
function greet(name: string, message?: string) {
  console.log(message ? `${message}, ${name}` : `Hello, ${name}`);
}

// 默认参数
function greet(name: string, message: string = "Hello") {
  console.log(`${message}, ${name}`);
}

5.1.2 回调函数参数类型

回调函数类型定义:

typescript
function processData(data: any[], callback: (item: any) => void) {
  data.forEach(callback);
}

processData([1, 2, 3], function (item) {
  console.log(item);
});

带返回值的回调函数:

typescript
function transformData(data: any[], transformer: (item: any) => any): any[] {
  return data.map(transformer);
}

let doubled = transformData([1, 2, 3], function (item) {
  return item * 2;
});

使用泛型增强回调函数:

typescript
function processData<T, U>(data: T[], callback: (item: T) => U): U[] {
  return data.map(callback);
}

let doubled: number[] = processData([1, 2, 3], function (item) {
  return item * 2;
});

5.1.3 函数参数验证

使用类型断言进行参数验证:

typescript
function assertIsString(value: unknown): asserts value is string {
  if (typeof value !== "string") {
    throw new Error("Value is not a string");
  }
}

function processString(value: unknown) {
  assertIsString(value);
  // 现在可以安全地将value视为string类型
  console.log(value.toUpperCase());
}

使用类型谓词:

typescript
function isString(value: unknown): value is string {
  return typeof value === "string";
}

function processString(value: unknown) {
  if (isString(value)) {
    // 现在可以安全地将value视为string类型
    console.log(value.toUpperCase());
  } else {
    throw new Error("Value is not a string");
  }
}

5.2 类属性类型应用场景

5.2.1 类属性类型的最佳实践

明确属性类型:

typescript
// 好的实践:明确属性类型
class Person {
  name: string;
  age: number;

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

// 不好的实践:使用any类型,失去类型安全
class Person {
  name: any;
  age: any;

  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
}

使用可选属性:

typescript
class Person {
  name: string;
  age?: number; // 可选属性

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

使用只读属性:

typescript
class Person {
  readonly id: number;
  name: string;

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

5.2.2 类属性的初始化与验证

属性初始化:

typescript
class Person {
  name: string = "Unknown"; // 直接初始化
  age: number;

  constructor(age: number) {
    this.age = age; // 在构造函数中初始化
  }
}

属性验证:

typescript
class Person {
  private _age: number;

  get age(): number {
    return this._age;
  }

  set age(value: number) {
    if (value < 0) {
      throw new Error("Age cannot be negative");
    }
    this._age = value;
  }
}

let person = new Person();
person.age = 30; // 正确
// person.age = -5; // 错误:抛出异常

5.2.3 类的继承与属性类型

子类属性类型:

typescript
class Animal {
  name: string;

  constructor(name: string) {
    this.name = name;
  }
}

class Dog extends Animal {
  breed: string; // 子类新增属性

  constructor(name: string, breed: string) {
    super(name); // 调用父类构造函数
    this.breed = breed;
  }
}

重写父类属性:

typescript
class Animal {
  color: string = "black"; // 父类属性

  makeSound(): void {
    console.log("...");
  }
}

class Dog extends Animal {
  color: string = "brown"; // 重写父类属性

  makeSound(): void {
    console.log("Woof!"); // 重写父类方法
  }
}

5.3 类型安全的异步编程

5.3.1 Promise 类型定义

Promise 类型语法:

typescript
function fetchData(): Promise<string> {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve("Data fetched successfully");
    }, 1000);
  });
}

使用 async/await:

typescript
async function processData() {
  try {
    let data = await fetchData();
    console.log(data);
  } catch (error) {
    console.error("Error:", error);
  }
}

5.3.2 类型安全的回调函数

使用泛型回调函数:

typescript
function delay<T>(value: T, timeout: number): Promise<T> {
  return new Promise((resolve) => {
    setTimeout(() => resolve(value), timeout);
  });
}

delay("Hello", 1000).then((data: string) => {
  console.log(data); // 类型安全
});

使用类型断言:

typescript
function processData(callback: (data: any) => void) {
  // 模拟异步数据获取
  setTimeout(() => {
    callback("Data from server");
  }, 1000);
}

processData((data: string) => {
  console.log(data); // 类型安全
});

5.3.3 异步函数的错误处理

错误处理最佳实践:

typescript
async function fetchData(): Promise<string> {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      Math.random() > 0.5 ? resolve("Success") : reject("Failure");
    }, 1000);
  });
}

async function processData() {
  try {
    let data = await fetchData();
    console.log("Data:", data);
  } catch (error) {
    console.error("Error:", error);
  }
}

使用 try...catch 包裹 Promise 链:

typescript
function fetchData(): Promise<string> {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      Math.random() > 0.5 ? resolve("Success") : reject("Failure");
    }, 1000);
  });
}

fetchData()
  .then((data) => {
    console.log("Data:", data);
  })
  .catch((error) => {
    console.error("Error:", error);
  });

5.4 大型项目中的类型设计模式

5.4.1 类型安全的状态管理

使用接口定义状态结构:

typescript
interface UserState {
  users: User[];
  loading: boolean;
  error: string | null;
}

let initialState: UserState = {
  users: [],
  loading: false,
  error: null,
};

使用泛型定义状态管理工具:

typescript
class StateManager<T> {
  private state: T;

  constructor(initialState: T) {
    this.state = initialState;
  }

  getState(): T {
    return this.state;
  }

  updateState(newState: Partial<T>): void {
    this.state = { ...this.state, ...newState };
  }
}

let userStateManager = new StateManager(initialState);
userStateManager.updateState({ loading: true });

5.4.2 类型安全的配置管理

使用接口定义配置结构:

typescript
interface AppConfig {
  apiUrl: string;
  timeout: number;
  debug: boolean;
}

let config: AppConfig = {
  apiUrl: "https://api.example.com",
  timeout: 5000,
  debug: false,
};

配置验证:

typescript
function validateConfig(config: Partial<AppConfig>): AppConfig {
  const defaultConfig: AppConfig = {
    apiUrl: "https://api.example.com",
    timeout: 5000,
    debug: false,
  };

  return { ...defaultConfig, ...config };
}

let userConfig = validateConfig({ timeout: 3000 });
console.log(userConfig); // 输出:{ apiUrl: "https://api.example.com", timeout: 3000, debug: false }

5.4.3 类型安全的依赖注入

使用接口定义依赖:

typescript
interface Logger {
  log(message: string): void;
  error(message: string): void;
}

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

  error(message: string): void {
    console.error(message);
  }
}

class Service {
  private logger: Logger;

  constructor(logger: Logger) {
    this.logger = logger;
  }

  doSomething(): void {
    this.logger.log("Doing something...");
    // ...
  }
}

// 使用依赖注入
let service = new Service(new ConsoleLogger());

依赖注入容器:

typescript
class Container {
  private services = new Map<string, any>();

  register<T>(key: string, implementation: new () => T): void {
    this.services.set(key, implementation);
  }

  resolve<T>(key: string): T {
    let implementation = this.services.get(key);
    if (!implementation) {
      throw new Error(`Service ${key} not registered`);
    }
    return new implementation();
  }
}

// 注册服务
let container = new Container();
container.register<Logger>("Logger", ConsoleLogger);
container.register<Service>("Service", Service);

// 解析服务
let service = container.resolve<Service>("Service");

5.5 类型系统的最佳实践

5.5.1 类型别名与接口的选择

选择接口的情况:

  • 描述对象结构时
  • 需要类型扩展时
  • 需要与其他接口组合时

选择类型别名的情况:

  • 定义联合类型时
  • 定义元组类型时
  • 定义原始类型别名时
  • 需要描述函数类型时

示例:

typescript
// 好的实践:使用接口描述对象结构
interface Point {
  x: number;
  y: number;
}

// 好的实践:使用类型别名定义联合类型
type MaybeNumber = number | null | undefined;

5.5.2 避免过度使用类型断言

好的实践:

typescript
function getSafeValue(value: unknown): string {
  if (typeof value === "string") {
    return value;
  }
  throw new Error("Value is not a string");
}

不好的实践:

typescript
function getValue(value: unknown): string {
  return value as string; // 不进行类型检查的类型断言
}

5.5.3 使用严格模式

启用 TypeScript 的严格模式(--strict)是最佳实践,它会启用一系列严格的类型检查选项。

严格模式选项:

  • --strictNullChecks:严格的空值检查
  • --noImplicitAny:禁止隐式的 any 类型
  • --strictFunctionTypes:严格的函数类型检查
  • --strictBindCallApply:严格的 bind/call/apply 检查
  • --strictPropertyInitialization:严格的属性初始化检查
  • --noImplicitThis:禁止隐式的 this 类型

示例: 在 tsconfig.json 中启用严格模式:

json
{
  "compilerOptions": {
    "strict": true
  }
}

六、总结与学习路径

6.1 核心知识点总结

基础类型:

  • 原始类型:number、string、boolean、null、undefined、void、never
  • 复合类型:数组、元组、对象、枚举
  • 特殊类型:any、unknown、字面量类型

高级类型:

  • 联合类型(|)和交叉类型(&)
  • 类型别名和接口
  • 泛型
  • 条件类型
  • 映射类型

函数类型:

  • 参数类型:必选、可选、默认、剩余参数
  • 函数重载
  • 回调函数类型

类与接口:

  • 类的属性和方法类型
  • 类的继承和多态
  • 接口实现和扩展
  • 抽象类和抽象方法

最佳实践:

  • 使用严格模式
  • 明确类型定义
  • 避免过度使用 any 类型
  • 合理使用类型断言
  • 使用泛型提高代码复用性

6.2 学习路径建议

入门阶段(1-2 周):

  • 掌握基础类型和变量声明
  • 学习函数类型和参数类型
  • 理解接口和类的基本概念
  • 实践简单的类型断言和联合类型

进阶阶段(2-4 周):

  • 深入学习泛型和条件类型
  • 掌握映射类型和工具类型
  • 理解类的继承、多态和访问修饰符
  • 实践类型安全的函数重载和回调函数

专家阶段(4 周以上):

  • 深入理解类型推断和类型系统原理
  • 掌握高级类型模式(如依赖注入、状态管理)
  • 实践大型项目中的类型设计模式
  • 探索 TypeScript 的高级特性(如装饰器、元编程)

6.3 学习资源推荐

官方文档:

  • TypeScript 官方文档
  • TypeScript Handbook

书籍:

  • 《TypeScript 权威指南》
  • 《Effective TypeScript》
  • 《Pro TypeScript》

在线课程:

  • TypeScript in 50 Shades of Red
  • Advanced TypeScript

社区资源:

  • TypeScript Deep Dive
  • Awesome TypeScript
  • TypeScript Weekly

实践项目:

  • 参与开源 TypeScript 项目
  • 使用 TypeScript 重构现有 JavaScript 项目
  • 开发一个全栈 TypeScript 应用(前端 + 后端)

通过系统学习和不断实践,你将能够掌握 TypeScript 的核心概念和高级特性,写出更加健壮、可维护的代码,为成为前端架构师打下坚实的基础。记住,TypeScript 是一个渐进式的语言,可以根据项目需求逐步引入,不要期望一蹴而就,而是通过不断的实践和应用来加深理解。

Released under the MIT License.