TypeScript-重新学习-20260407

4 阅读15分钟

TypeScript

一、TypeScript 基础

1. TypeScript 概述与优势

问题:TypeScript 与 JavaScript 的区别是什么?TypeScript 的优势有哪些?TypeScript 的类型系统特点是什么?TypeScript 的编译过程是怎样的?

答案

TypeScript vs JavaScript 对比表

特性TypeScriptJavaScript
类型系统静态类型,编译时类型检查动态类型,运行时类型检查
错误检测编译时报错,提前发现错误运行时报错,调试困难
开发体验智能提示、代码补全、重构支持有限提示,依赖注释
兼容性JavaScript 的超集,完全兼容原生支持所有环境
学习成本需要学习类型语法门槛较低
构建步骤需要编译(tsc)可直接运行
项目规模适合大型项目、团队协作适合小型项目、快速原型

TypeScript 核心优势

  1. 类型安全:编译时类型检查,减少运行时错误
  2. 代码可维护性:类型作为文档,提高代码可读性
  3. 开发效率:智能提示、代码补全、重构支持
  4. 渐进式采用:可将现有 JS 项目逐步迁移到 TS
  5. 增强的面向对象:类、接口、泛型等更完善的支持

类型系统特点

  • 静态类型:编译时进行类型检查
  • 类型推断:自动推断变量类型,减少类型注解
  • 结构化类型:基于形状(结构)进行类型检查,而非名义类型
  • 类型兼容性:基于结构子类型,灵活性高

编译过程

// 源码:app.ts
const greet = (name: string): string => {
  return `Hello, ${name}!`;
};

// 编译步骤:
// 1. 词法分析:将源码分解为词法单元(tokens)
// 2. 语法分析:生成抽象语法树(AST)
// 3. 语义分析:类型检查、符号解析
// 4. 转换:将 TypeScript AST 转换为 JavaScript AST
// 5. 代码生成:生成 JavaScript 代码
// 6. 输出:app.js
const greet = (name) => {
  return `Hello, ${name}!`;
};

编译命令

# 编译单个文件
tsc app.ts

# 编译整个项目(使用 tsconfig.json)
tsc

# 监听模式,自动重新编译
tsc --watch

# 生成声明文件
tsc --declaration

# 严格模式
tsc --strict

补充说明

  • TypeScript 是 JavaScript 的超集,所有合法的 JavaScript 代码都是合法的 TypeScript 代码
  • TypeScript 编译后会移除所有类型注解,生成纯 JavaScript 代码
  • 可以使用 tsconfig.json 配置文件来定制编译选项
  • 对于小型项目,TypeScript 可能增加复杂性;对于大型项目,TypeScript 能显著提高代码质量

二、类型系统基础

2. 基本类型与类型注解

问题:TypeScript 的基本类型有哪些?TypeScript 类型注解的语法是什么?

答案

TypeScript 基本类型

类型描述示例
number数字(整数、浮点数)let age: number = 25;
string字符串let name: string = "Alice";
boolean布尔值let isDone: boolean = true;
null空值let n: null = null;
undefined未定义let u: undefined = undefined;
symbol唯一标识符let sym: symbol = Symbol("key");
bigint大整数let big: bigint = 100n;
any任意类型(禁用类型检查)let anything: any = "whatever";
unknown未知类型(安全版 any)let unsure: unknown = "something";
void无返回值function warn(): void { console.log("warn"); }
never永不返回function error(message: string): never { throw new Error(message); }
object非原始类型let obj: object = { x: 1, y: 2 };
array数组let list: number[] = [1, 2, 3];
tuple元组(固定长度数组)let tuple: [string, number] = ["hello", 10];
enum枚举enum Color { Red, Green, Blue };

类型注解语法

// 变量类型注解
let name: string = "Alice";
let age: number = 30;
let isStudent: boolean = true;

// 函数参数和返回值类型注解
function greet(name: string): string {
  return `Hello, ${name}`;
}

// 对象类型注解
let person: { name: string; age: number } = {
  name: "Bob",
  age: 25
};

// 数组类型注解
let numbers: number[] = [1, 2, 3];
let strings: Array<string> = ["a", "b", "c"]; // 泛型语法

// 可选参数
function printName(firstName: string, lastName?: string): void {
  console.log(`${firstName} ${lastName || ''}`);
}

// 默认参数(会自动推断类型)
function multiply(x: number, y: number = 10): number {
  return x * y;
}

// 剩余参数
function sum(...numbers: number[]): number {
  return numbers.reduce((acc, cur) => acc + cur, 0);
}

// 箭头函数类型注解
const add = (x: number, y: number): number => x + y;

类型推断

// TypeScript 可以自动推断类型
let x = 10; // 推断为 number
let y = "hello"; // 推断为 string
let z = true; // 推断为 boolean

// 函数返回值类型推断
function square(x: number) {
  return x * x; // 推断返回值为 number
}

// 对象属性类型推断
const user = {
  name: "Alice", // 推断为 string
  age: 25 // 推断为 number
};

补充说明

  • 类型注解是可选的,TypeScript 会尽可能进行类型推断
  • 使用冒号 : 进行类型注解,放在变量名、参数或返回值之后
  • 优先使用类型推断,只在必要时添加类型注解
  • any 类型会禁用类型检查,应尽量避免使用

三、接口与类型别名

3. 接口 vs 类型别名

问题:TypeScript 接口与类型的区别是什么?TypeScript 类型别名的作用是什么?

答案

接口(interface)

  • 用于定义对象形状(对象、类、函数的类型)
  • 支持继承(extends)、实现(implements)
  • 可重复声明,会自动合并
  • 主要描述对象结构

类型别名(type)

  • 为任意类型创建别名(不仅限于对象)
  • 不支持继承,但可通过交叉类型(&)实现类似效果
  • 不可重复声明
  • 更灵活,支持联合类型、元组、基本类型等

对比表

特性接口(interface)类型别名(type)
语法interface User { name: string }type User = { name: string }
扩展interface Admin extends User {}type Admin = User & { role: string }
实现类可以实现接口不能直接实现
合并支持声明合并不支持,会报错
适用场景对象形状、类约束任意类型别名、联合类型、复杂类型
性能更好(早期版本差异)几乎相同(现代版本)

代码示例

// 1. 接口定义
interface Person {
  name: string;
  age: number;
}

// 接口继承
interface Employee extends Person {
  employeeId: string;
  department: string;
}

// 接口合并(声明合并)
interface User {
  name: string;
}

interface User {
  age: number;
}

// 合并结果:
// interface User {
//   name: string;
//   age: number;
// }

// 2. 类型别名定义
type Point = {
  x: number;
  y: number;
};

// 联合类型(接口无法实现)
type Status = "pending" | "success" | "error";

// 元组类型
type Tuple = [string, number];

// 交叉类型(实现类似继承)
type Animal = {
  name: string;
};

type Dog = Animal & {
  breed: string;
  bark(): void;
};

// 3. 函数类型
interface MathFunc {
  (x: number, y: number): number;
}

type MathFuncType = (x: number, y: number) => number;

// 两者都可以用于定义函数类型
const add: MathFunc = (a, b) => a + b;
const multiply: MathFuncType = (a, b) => a * b;

// 4. 类型别名支持更复杂的类型
type StringOrNumber = string | number;
type Nullable<T> = T | null;
type RecordType = Record<string, number>;

最佳实践

  1. 优先使用接口:定义对象形状、类约束时
  2. 使用类型别名:定义联合类型、元组、复杂类型时
  3. 一致性:项目中选择一种风格并保持统一
  4. 何时选择
    • 需要声明合并 → 使用接口
    • 需要扩展类型 → 两者都可,接口更直观
    • 定义基本类型别名 → 使用类型别名
    • 定义函数类型 → 两者都可,语法略有不同

补充说明

  • 现代的 TypeScript 中,接口和类型别名在性能上差异很小
  • 声明合并是接口的重要特性,常用于扩展第三方库的类型定义
  • 类型别名支持模板字面量类型、条件类型等高级特性

四、高级类型

4. 联合类型、交叉类型、字面量类型与枚举

问题:TypeScript 联合类型的使用?TypeScript 交叉类型的使用?TypeScript 字面量类型的使用?TypeScript 枚举的作用和使用场景?

答案

联合类型(Union Types)

  • 表示值可以是多种类型之一
  • 使用竖线 | 分隔类型
// 基本联合类型
type StringOrNumber = string | number;

function format(value: StringOrNumber): string {
  if (typeof value === "string") {
    return value.toUpperCase();
  } else {
    return value.toFixed(2);
  }
}

// 联合类型与类型守卫
type Shape = 
  | { kind: "circle"; radius: number }
  | { kind: "rectangle"; width: number; height: number }
  | { kind: "square"; size: number };

function area(shape: Shape): number {
  switch (shape.kind) {
    case "circle":
      return Math.PI * shape.radius ** 2;
    case "rectangle":
      return shape.width * shape.height;
    case "square":
      return shape.size ** 2;
  }
}

// 可辨识联合(Discriminated Unions)
// 通过共同的字段(kind)来区分不同类型

交叉类型(Intersection Types)

  • 表示值必须同时满足多个类型
  • 使用 & 连接类型
// 基本交叉类型
interface Named {
  name: string;
}

interface Aged {
  age: number;
}

type Person = Named & Aged;

const person: Person = {
  name: "Alice",
  age: 25
};

// 交叉类型合并
type A = { a: string };
type B = { b: number };
type C = { c: boolean };

type ABC = A & B & C; // { a: string; b: number; c: boolean }

// 函数类型的交叉
type F1 = (x: number) => number;
type F2 = (y: string) => string;

type F3 = F1 & F2; // 既是 F1 也是 F2(实际上不可能,但类型系统允许)

字面量类型(Literal Types)

  • 具体的值作为类型
  • 字符串字面量、数字字面量、布尔字面量
// 字符串字面量类型
type Direction = "up" | "down" | "left" | "right";

function move(direction: Direction): void {
  console.log(`Moving ${direction}`);
}

move("up"); // OK
move("north"); // Error: Argument of type '"north"' is not assignable...

// 数字字面量类型
type DiceValue = 1 | 2 | 3 | 4 | 5 | 6;

const rollDice = (): DiceValue => {
  return Math.floor(Math.random() * 6 + 1) as DiceValue;
};

// 布尔字面量类型(较少使用)
type TrueOnly = true;

// 与联合类型结合
type StatusCode = 200 | 301 | 400 | 404 | 500;
type Method = "GET" | "POST" | "PUT" | "DELETE";

interface RequestConfig {
  method: Method;
  url: string;
  timeout?: number;
}

枚举(Enum)

  • 定义命名常量集合
  • 有数字枚举和字符串枚举两种
// 1. 数字枚举(默认)
enum Direction {
  Up,    // 0
  Down,  // 1
  Left,  // 2
  Right  // 3
}

// 自定义起始值
enum Status {
  Pending = 1,  // 1
  Approved,     // 2
  Rejected      // 3
}

// 2. 字符串枚举
enum LogLevel {
  Error = "ERROR",
  Warn = "WARN",
  Info = "INFO",
  Debug = "DEBUG"
}

// 3. 常量枚举(编译时完全删除,性能更好)
const enum MediaType {
  JSON = "application/json",
  XML = "application/xml",
  Form = "application/x-www-form-urlencoded"
}

// 4. 异构枚举(混合字符串和数字,不推荐)
enum BooleanLike {
  No = 0,
  Yes = "YES"
}

// 枚举的编译结果
enum Color {
  Red,
  Green,
  Blue
}

// 编译为:
// var Color;
// (function (Color) {
//   Color[Color["Red"] = 0] = "Red";
//   Color[Color["Green"] = 1] = "Green";
//   Color[Color["Blue"] = 2] = "Blue";
// })(Color || (Color = {}));

// 使用示例
function setDirection(direction: Direction): void {
  console.log(`Setting direction to ${Direction[direction]}`);
}

setDirection(Direction.Left); // "Setting direction to Left"

// 枚举反向映射
console.log(Direction.Up); // 0
console.log(Direction[0]); // "Up"
console.log(Direction["Up"]); // 0

最佳实践

// 1. 使用联合类型代替枚举的简单场景
type Direction = "up" | "down" | "left" | "right";

// 优点:更轻量,无需运行时对象,与普通字符串兼容

// 2. 需要使用反向映射时使用枚举
enum HttpMethod {
  GET = "GET",
  POST = "POST",
  PUT = "PUT",
  DELETE = "DELETE"
}

// 3. 常量枚举用于性能优化
const enum ResponseCode {
  Success = 200,
  NotFound = 404,
  ServerError = 500
}

补充说明

  • 联合类型是 TypeScript 类型系统的核心,支持强大的类型守卫
  • 交叉类型常用于组合多个类型,实现类似“多重继承”
  • 字面量类型可以与模板字面量类型结合,创建复杂的字符串模式
  • 枚举会生成运行时代码,增加包体积,考虑使用联合类型替代

五、特殊类型

5. any vs unknown vs void vs never

问题:TypeScript any 类型与 unknown 类型的区别?TypeScript void 类型与 never 类型的区别?

答案

any vs unknown 对比表

特性anyunknown
类型检查禁用所有类型检查保持类型安全
赋值可以赋给任意类型只能赋给 unknown 或 any
操作允许任意操作不允许任何操作(需先类型断言)
安全性不安全,可能运行时错误安全,强制类型检查
使用场景迁移旧代码、第三方库替代 any,提高类型安全

代码示例

// any:禁用类型检查
let value: any = "hello";
value = 42; // OK
value.toUpperCase(); // 编译时OK,运行时OK(String方法)
value.foo.bar.baz(); // 编译时OK,运行时可能报错

// unknown:类型安全
let unsure: unknown = "hello";
unsure = 42; // OK
// unsure.toUpperCase(); // 编译时报错:Object is of type 'unknown'

// 使用 unknown 需要类型检查或类型断言
if (typeof unsure === "string") {
  console.log(unsure.toUpperCase()); // OK,经过类型守卫
}

let str: string = unsure as string; // 类型断言
let num: number = <number>unsure; // 另一种断言语法

// unknown 比 any 更安全
function process(value: unknown): void {
  if (typeof value === "string") {
    console.log(value.length);
  } else if (Array.isArray(value)) {
    console.log(value.length);
  }
  // 必须检查类型后才能操作
}

void vs never 对比表

特性voidnever
含义没有返回值永远不会返回
可以是 undefined 或 null(严格模式仅 undefined)没有值
使用场景函数没有返回值抛出异常、无限循环、类型检查中的不可能情况
返回值允许返回 undefined不允许返回任何值

代码示例

// void:函数没有返回值
function logMessage(message: string): void {
  console.log(message);
  // 隐式返回 undefined
}

function noReturn(): void {
  // 没有 return 语句
}

// 注意:void 类型的变量只能赋值为 undefined 或 null(非严格模式)
let unusable: void = undefined;
// unusable = null; // 严格模式下报错

// never:永远不会返回的函数
function throwError(message: string): never {
  throw new Error(message);
  // 永远不会执行到函数末尾
}

function infiniteLoop(): never {
  while (true) {
    // 无限循环
  }
  // 永远不会返回
}

// never 在类型系统中的应用
type NonNullable<T> = T extends null | undefined ? never : T;

// 条件类型中排除 null 和 undefined
type T1 = NonNullable<string | null | undefined>; // string

// 穷尽性检查
type Shape = "circle" | "square" | "triangle";

function getArea(shape: Shape): number {
  switch (shape) {
    case "circle":
      return Math.PI;
    case "square":
      return 1;
    case "triangle":
      return 0.5;
    default:
      // 类型检查确保所有情况都已处理
      const exhaustiveCheck: never = shape;
      throw new Error(`Unhandled shape: ${exhaustiveCheck}`);
  }
}

最佳实践

  1. 避免使用 any:尽可能使用具体的类型
  2. 使用 unknown 替代 any:当类型不确定时,使用 unknown 提高安全性
  3. 合理使用 void:表示函数没有返回值
  4. 善用 never:用于永远不会返回的函数和类型系统的高级特性

补充说明

  • any 会破坏 TypeScript 的类型安全性,应尽量避免
  • unknown 是 TypeScript 3.0 引入的类型安全的 any 替代品
  • never 类型在条件类型、映射类型中非常有用,是 TypeScript 类型系统的重要基础

六、泛型

6. 泛型基础与约束

问题:TypeScript 泛型的作用是什么?TypeScript 泛型约束的使用?

答案

泛型的作用

  1. 类型复用:创建可重用的组件,适用于多种类型
  2. 类型安全:保持类型信息,避免使用 any
  3. 代码灵活:允许用户指定具体类型
  4. 约束行为:通过约束限制可用的类型

基本语法

// 1. 泛型函数
function identity<T>(value: T): T {
  return value;
}

// 使用类型参数显式指定
let output1 = identity<string>("hello"); // 输出类型为 string

// 使用类型推断
let output2 = identity(42); // 输出类型为 number

// 2. 泛型接口
interface Box<T> {
  content: T;
}

let stringBox: Box<string> = { content: "hello" };
let numberBox: Box<number> = { content: 42 };

// 3. 泛型类
class Container<T> {
  private value: T;
  
  constructor(value: T) {
    this.value = value;
  }
  
  getValue(): T {
    return this.value;
  }
  
  setValue(newValue: T): void {
    this.value = newValue;
  }
}

const stringContainer = new Container("hello");
const numberContainer = new Container(42);

// 4. 泛型约束
interface Lengthwise {
  length: number;
}

// 约束 T 必须具有 length 属性
function loggingIdentity<T extends Lengthwise>(arg: T): T {
  console.log(arg.length);
  return arg;
}

loggingIdentity("hello"); // OK,字符串有 length
loggingIdentity([1, 2, 3]); // OK,数组有 length
// loggingIdentity(42); // Error,数字没有 length

// 5. 多个类型参数
function swap<T, U>(tuple: [T, U]): [U, T] {
  return [tuple[1], tuple[0]];
}

const swapped = swap(["hello", 42]); // 类型为 [number, string]

// 6. 泛型约束中使用 keyof
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

const person = { name: "Alice", age: 25 };
getProperty(person, "name"); // OK,返回 string
getProperty(person, "age"); // OK,返回 number
// getProperty(person, "salary"); // Error,"salary" 不是 person 的键

// 7. 泛型默认类型
interface PaginatedResponse<T = any> {
  data: T[];
  total: number;
  page: number;
  pageSize: number;
}

// 使用默认类型
const response1: PaginatedResponse = {
  data: [1, 2, 3],
  total: 3,
  page: 1,
  pageSize: 10
};

// 指定具体类型
interface User {
  id: number;
  name: string;
}

const response2: PaginatedResponse<User> = {
  data: [{ id: 1, name: "Alice" }],
  total: 1,
  page: 1,
  pageSize: 10
};

// 8. 条件类型中的泛型
type IsString<T> = T extends string ? "yes" : "no";

type T1 = IsString<string>; // "yes"
type T2 = IsString<number>; // "no"

// 9. 映射类型中的泛型
type Readonly<T> = {
  readonly [P in keyof T]: T[P];
};

type Partial<T> = {
  [P in keyof T]?: T[P];
};

// 应用
interface Todo {
  title: string;
  description: string;
  completed: boolean;
}

type ReadonlyTodo = Readonly<Todo>;
type PartialTodo = Partial<Todo>;

泛型约束的高级用法

// 1. 构造函数约束
interface Constructor<T> {
  new(...args: any[]): T;
}

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

class Person {
  constructor(public name: string) {}
}

const person = createInstance(Person, "Alice"); // Person 实例

// 2. 多个约束
interface Serializable {
  serialize(): string;
}

interface Deserializable {
  deserialize(data: string): void;
}

function process<T extends Serializable & Deserializable>(obj: T): void {
  const data = obj.serialize();
  // 处理数据
  obj.deserialize(data);
}

// 3. 约束为特定类型
function merge<T extends object, U extends object>(first: T, second: U): T & U {
  return { ...first, ...second };
}

const merged = merge({ name: "Alice" }, { age: 25 });
// 类型为 { name: string } & { age: number }

补充说明

  • 泛型是 TypeScript 最强大的特性之一,是构建可复用类型安全代码的基础
  • 合理使用泛型约束,避免过于宽松的类型
  • 泛型与条件类型、映射类型结合可以实现复杂的类型操作

七、工具类型

7. 常用工具类型详解

问题:TypeScript 工具类型有哪些?Partial、Required、Readonly 类型的使用?Pick、Omit 类型的使用?Record 类型的使用?Exclude、Extract 类型的使用?ReturnType、Parameters 类型的使用?NonNullable 类型的使用?

答案

内置工具类型概览

工具类型作用示例
Partial<T>将所有属性变为可选Partial<User>{ name?: string; age?: number }
Required<T>将所有属性变为必选Required<PartialUser>{ name: string; age: number }
Readonly<T>将所有属性变为只读Readonly<User>{ readonly name: string; readonly age: number }
Pick<T, K>从 T 中选取部分属性 KPick<User, "name">{ name: string }
Omit<T, K>从 T 中排除部分属性 KOmit<User, "age">{ name: string }
Record<K, T>创建键类型为 K,值类型为 T 的对象Record<string, number>{ [key: string]: number }
Exclude<T, U>从 T 中排除可赋值给 U 的类型Exclude<"a"|"b"|"c", "a">"b" | "c"
Extract<T, U>提取 T 中可赋值给 U 的类型Extract<"a"|"b"|"c", "a"|"d">"a"
NonNullable<T>从 T 中排除 null 和 undefinedNonNullable<string | null>string
ReturnType<T>获取函数返回值类型ReturnType<typeof setTimeout>number
Parameters<T>获取函数参数类型元组Parameters<(x: number, y: string) => void>[number, string]
ConstructorParameters<T>获取构造函数参数类型元组ConstructorParameters<typeof Error>[string?]
InstanceType<T>获取构造函数实例类型InstanceType<typeof Array>any[]
ThisParameterType<T>获取函数 this 参数类型
OmitThisParameter<T>移除函数 this 参数类型

详细示例

// 基础类型
interface User {
  id: number;
  name: string;
  age: number;
  email?: string;
}

// 1. Partial - 所有属性变为可选
type PartialUser = Partial<User>;
// 等价于:{ id?: number; name?: string; age?: number; email?: string }

// 2. Required - 所有属性变为必选
type RequiredUser = Required<User>;
// 等价于:{ id: number; name: string; age: number; email: string }

// 3. Readonly - 所有属性变为只读
type ReadonlyUser = Readonly<User>;
// 等价于:{ readonly id: number; readonly name: string; readonly age: number; readonly email?: string }

// 4. Pick - 选择部分属性
type UserBasicInfo = Pick<User, "id" | "name">;
// 等价于:{ id: number; name: string }

// 5. Omit - 排除部分属性
type UserWithoutId = Omit<User, "id">;
// 等价于:{ name: string; age: number; email?: string }

// 6. Record - 创建记录类型
type PageInfo = Record<"home" | "about" | "contact", string>;
// 等价于:{ home: string; about: string; contact: string }

type Dictionary<T> = Record<string, T>;
// 等价于:{ [key: string]: T }

// 7. Exclude - 排除类型
type T0 = Exclude<"a" | "b" | "c", "a">; // "b" | "c"
type T1 = Exclude<string | number | (() => void), Function>; // string | number
type T2 = Exclude<User | null | undefined, null | undefined>; // User

// 8. Extract - 提取类型
type T3 = Extract<"a" | "b" | "c", "a" | "c">; // "a" | "c"
type T4 = Extract<string | number | (() => void), Function>; // () => void

// 9. NonNullable - 排除 null 和 undefined
type T5 = NonNullable<string | number | null | undefined>; // string | number

// 10. ReturnType - 函数返回值类型
type T6 = ReturnType<() => string>; // string
type T7 = ReturnType<<T>() => T>; // unknown
type T8 = ReturnType<<T extends U, U extends number[]>() => T>; // number[]

// 实际应用
declare function f1(): { a: number; b: string };
type T9 = ReturnType<typeof f1>; // { a: number; b: string }

// 11. Parameters - 函数参数类型
type T10 = Parameters<() => string>; // []
type T11 = Parameters<(s: string) => void>; // [string]
type T12 = Parameters<<T>(arg: T) => T>; // [unknown]

// 实际应用
declare function f2(arg: { a: number; b: string }): void;
type T13 = Parameters<typeof f2>; // [{ a: number; b: string }]

// 12. 组合使用工具类型
type UserPreview = Pick<User, "id" | "name"> & Partial<Pick<User, "email">>;
// 等价于:{ id: number; name: string; email?: string }

type ReadonlyPartial<T> = Readonly<Partial<T>>;
type Nullable<T> = T | null | undefined;
type MaybeArray<T> = T | T[];

// 13. 自定义工具类型
type DeepPartial<T> = {
  [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
};

type DeepReadonly<T> = {
  readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P];
};

type ValueOf<T> = T[keyof T];

// 应用
interface Company {
  name: string;
  address: {
    city: string;
    street: string;
  };
}

type DeepPartialCompany = DeepPartial<Company>;
// 等价于:{
//   name?: string;
//   address?: {
//     city?: string;
//     street?: string;
//   };
// }

// 14. 条件类型与工具类型结合
type Diff<T, U> = T extends U ? never : T;
type Filter<T, U> = T extends U ? T : never;

type T14 = Diff<"a" | "b" | "c", "a" | "b">; // "c"
type T15 = Filter<"a" | "b" | "c", "a" | "b">; // "a" | "b"

// 15. 模板字面量类型与工具类型(TypeScript 4.1+)
type EventName = "click" | "scroll" | "mousemove";
type HandlerName = `on${Capitalize<EventName>}`;
// 等价于:"onClick" | "onScroll" | "onMousemove"

// 16. 类型谓词工具类型
type Predicate<T> = (value: unknown) => value is T;

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

type StringPredicate = Predicate<string>;

手写实现(理解原理)

// Partial 实现原理
type MyPartial<T> = {
  [P in keyof T]?: T[P];
};

// Required 实现原理
type MyRequired<T> = {
  [P in keyof T]-?: T[P];
};

// Readonly 实现原理
type MyReadonly<T> = {
  readonly [P in keyof T]: T[P];
};

// Pick 实现原理
type MyPick<T, K extends keyof T> = {
  [P in K]: T[P];
};

// Omit 实现原理(通过 Pick 和 Exclude)
type MyOmit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;

// Record 实现原理
type MyRecord<K extends keyof any, T> = {
  [P in K]: T;
};

// Exclude 实现原理
type MyExclude<T, U> = T extends U ? never : T;

// Extract 实现原理
type MyExtract<T, U> = T extends U ? T : never;

// NonNullable 实现原理
type MyNonNullable<T> = T extends null | undefined ? never : T;

// ReturnType 实现原理
type MyReturnType<T extends (...args: any) => any> = 
  T extends (...args: any) => infer R ? R : any;

// Parameters 实现原理
type MyParameters<T extends (...args: any) => any> = 
  T extends (...args: infer P) => any ? P : never;

补充说明

  • 工具类型是 TypeScript 类型编程的核心,理解它们有助于编写更灵活的类型定义
  • 条件类型(extends ? :)和映射类型(in keyof)是构建工具类型的基础
  • infer 关键字用于在条件类型中提取类型信息
  • 工具类型可以组合使用,创建更复杂的类型操作

八、类型推断与断言

8. 类型推断、类型断言与类型守卫

问题:TypeScript 类型推断的规则?TypeScript 类型断言的使用?TypeScript 类型断言与类型转换的区别?TypeScript 类型守卫的使用?TypeScript 类型守卫的类型有哪些?TypeScript 类型谓词的使用?

答案

类型推断规则

  1. 基本类型推断:从赋值推断类型
  2. 最佳通用类型:从多个值推断联合类型
  3. 上下文类型:从上下文推断类型(如函数参数)
  4. 类型拓宽:字面量类型会拓宽为基本类型
// 1. 基本类型推断
let x = 3; // 推断为 number
let y = "hello"; // 推断为 string
let z = [1, 2, 3]; // 推断为 number[]

// 2. 最佳通用类型
let arr = [0, 1, null]; // 推断为 (number | null)[]
let mixed = ["hello", 42, true]; // 推断为 (string | number | boolean)[]

// 3. 上下文类型
window.onmousedown = function(event) {
  // event 被推断为 MouseEvent
  console.log(event.clientX, event.clientY);
};

// 4. 类型拓宽
const constStr = "hello"; // 类型为 "hello"(字面量类型)
let letStr = "hello"; // 类型为 string(拓宽)

// 使用 const 断言防止拓宽
let notWidened = "hello" as const; // 类型为 "hello"
let tuple = [1, 2] as const; // 类型为 readonly [1, 2]

类型断言

  • 告诉 TypeScript 你比它更了解类型信息
  • 两种语法:as<Type>
// 1. as 语法(推荐)
let someValue: any = "this is a string";
let strLength1: number = (someValue as string).length;

// 2. 尖括号语法(在 JSX 中会冲突)
let strLength2: number = (<string>someValue).length;

// 3. 非空断言操作符 !
let maybeString: string | null = getString();
let length: number = maybeString!.length; // 断言 maybeString 不为 null/undefined

// 4. 双重断言(类型断言链)
let value: unknown = "hello";
// let str: string = value; // Error: unknown 不能直接赋值给 string
let str1: string = value as string; // 需要类型断言
let str2: string = value as any as string; // 双重断言,绕过类型检查

// 5. const 断言
let point = {
  x: 10,
  y: 20
} as const; // 类型为 { readonly x: 10; readonly y: 20; }

// 实际应用场景
// 场景1:处理 DOM 元素
const canvas = document.getElementById("canvas") as HTMLCanvasElement;
const ctx = canvas.getContext("2d")!;

// 场景2:处理 API 响应
interface ApiResponse {
  data: User[];
  total: number;
}

fetch("/api/users")
  .then(res => res.json())
  .then(data => {
    const response = data as ApiResponse;
    console.log(response.total);
  });

// 场景3:处理第三方库
declare function getData(): unknown;
const result = getData() as { id: number; name: string };

类型断言 vs 类型转换

  • 类型断言:编译时概念,告诉编译器类型信息,不改变运行时的值
  • 类型转换:运行时操作,实际转换值的类型
// 类型断言(编译时)
let str: any = "123";
let num1: number = str as number; // 编译时通过,运行时 str 仍然是字符串

// 类型转换(运行时)
let num2: number = Number(str); // 运行时将字符串转换为数字
let num3: number = parseInt(str); // 运行时解析字符串为数字

// 正确使用类型转换
function processInput(input: string | number): number {
  if (typeof input === "string") {
    return parseFloat(input); // 类型转换
  }
  return input; // 直接返回数字
}

类型守卫

  • 运行时检查,用于缩小类型范围
  • 主要方式:typeofinstanceofin、自定义类型谓词
// 1. typeof 类型守卫
function padLeft(value: string, padding: string | number): string {
  if (typeof padding === "number") {
    return " ".repeat(padding) + value;
  }
  return padding + value;
}

// 2. instanceof 类型守卫
class Bird {
  fly() {
    console.log("flying");
  }
}

class Fish {
  swim() {
    console.log("swimming");
  }
}

function move(pet: Bird | Fish): void {
  if (pet instanceof Bird) {
    pet.fly();
  } else {
    pet.swim();
  }
}

// 3. in 操作符类型守卫
interface Cat {
  meow(): void;
}

interface Dog {
  bark(): void;
}

function makeSound(pet: Cat | Dog): void {
  if ("meow" in pet) {
    pet.meow();
  } else {
    pet.bark();
  }
}

// 4. 自定义类型守卫(类型谓词)
function isString(value: unknown): value is string {
  return typeof value === "string";
}

function isNumber(value: unknown): value is number {
  return typeof value === "number" && !isNaN(value);
}

interface Rectangle {
  kind: "rectangle";
  width: number;
  height: number;
}

interface Circle {
  kind: "circle";
  radius: number;
}

function isRectangle(shape: Rectangle | Circle): shape is Rectangle {
  return shape.kind === "rectangle";
}

function area(shape: Rectangle | Circle): number {
  if (isRectangle(shape)) {
    return shape.width * shape.height;
  } else {
    return Math.PI * shape.radius ** 2;
  }
}

// 5. 可辨识联合(Discriminated Unions)
type Shape = Rectangle | Circle;

function getArea(shape: Shape): number {
  switch (shape.kind) {
    case "rectangle":
      return shape.width * shape.height;
    case "circle":
      return Math.PI * shape.radius ** 2;
  }
}

// 6. 类型断言守卫(Type Assertion Guards)
function assertIsString(value: unknown): asserts value is string {
  if (typeof value !== "string") {
    throw new Error("Not a string!");
  }
}

function processValue(value: unknown): void {
  assertIsString(value);
  // 这里 value 被断言为 string
  console.log(value.toUpperCase());
}

类型谓词

  • 自定义类型守卫的函数返回值类型
  • 格式:parameterName is Type
// 复杂类型谓词
interface Admin {
  role: "admin";
  permissions: string[];
}

interface User {
  role: "user";
  name: string;
}

type Person = Admin | User;

function isAdmin(person: Person): person is Admin {
  return person.role === "admin";
}

function getPermissions(person: Person): string[] | null {
  if (isAdmin(person)) {
    return person.permissions; // person 类型为 Admin
  }
  return null; // person 类型为 User
}

// 数组类型谓词
function isStringArray(value: unknown): value is string[] {
  return Array.isArray(value) && value.every(item => typeof item === "string");
}

function processArray(arr: unknown): void {
  if (isStringArray(arr)) {
    // arr 类型为 string[]
    arr.forEach(str => console.log(str.toUpperCase()));
  }
}

// 使用类型谓词处理 API 响应
interface SuccessResponse<T> {
  success: true;
  data: T;
}

interface ErrorResponse {
  success: false;
  error: string;
}

type ApiResponse<T> = SuccessResponse<T> | ErrorResponse;

function isSuccessResponse<T>(
  response: ApiResponse<T>
): response is SuccessResponse<T> {
  return response.success;
}

async function fetchData<T>(url: string): Promise<T | null> {
  const response: ApiResponse<T> = await fetch(url).then(res => res.json());
  
  if (isSuccessResponse(response)) {
    return response.data;
  } else {
    console.error(response.error);
    return null;
  }
}

补充说明

  • 优先使用类型守卫而不是类型断言,类型守卫更安全可靠
  • 类型断言应在了解代码逻辑的情况下谨慎使用
  • 自定义类型谓词可以提高代码的类型安全性和可读性
  • 可辨识联合是处理复杂类型分支的优雅方式

九、装饰器

9. 装饰器类型与使用

问题:TypeScript 装饰器的作用?TypeScript 装饰器的类型有哪些?TypeScript 类装饰器、属性装饰器、方法装饰器、参数装饰器的使用?TypeScript 装饰器元数据?

答案

装饰器概述

  • 装饰器是特殊的声明,可以附加到类、方法、属性、参数上
  • 修改或扩展它们的行为
  • 实验性特性,需要在 tsconfig.json 中启用 "experimentalDecorators": true
  • 本质上是一个函数,在运行时被调用

装饰器类型

  1. 类装饰器:应用于类构造函数
  2. 方法装饰器:应用于方法
  3. 属性装饰器:应用于属性
  4. 参数装饰器:应用于参数
  5. 访问器装饰器:应用于 getter/setter

基本语法

// 装饰器工厂(带参数)
function DecoratorFactory(config: any) {
  return function(target: any) {
    // 装饰器逻辑
  };
}

// 装饰器应用
@DecoratorFactory({ option: true })
class MyClass {
  @PropertyDecorator()
  property: string;
  
  @MethodDecorator()
  method(@ParameterDecorator() param: string) {}
  
  @AccessorDecorator()
  get accessor() { return this.property; }
}

类装饰器

// 类装饰器签名
type ClassDecorator = <TFunction extends Function>(
  target: TFunction
) => TFunction | void;

// 示例1:简单类装饰器
function sealed(constructor: Function) {
  Object.seal(constructor);
  Object.seal(constructor.prototype);
}

@sealed
class Greeter {
  greeting: string;
  constructor(message: string) {
    this.greeting = message;
  }
  greet() {
    return "Hello, " + this.greeting;
  }
}

// 示例2:类装饰器工厂
function classDecoratorFactory(prefix: string) {
  return function<T extends { new(...args: any[]): {} }>(constructor: T) {
    return class extends constructor {
      className = prefix + constructor.name;
    };
  };
}

@classDecoratorFactory("MyPrefix-")
class MyClass {
  name = "Test";
}

const instance = new MyClass();
console.log((instance as any).className); // "MyPrefix-MyClass"

// 示例3:替换类实现
function replaceClass<T extends { new(...args: any[]): {} }>(constructor: T) {
  return class extends constructor {
    newProperty = "new property";
    hello = "override";
  };
}

@replaceClass
class Greeter2 {
  property = "property";
  hello: string;
  constructor(m: string) {
    this.hello = m;
  }
}

const g = new Greeter2("world");
console.log(g.hello); // "override"
console.log((g as any).newProperty); // "new property"

方法装饰器

// 方法装饰器签名
type MethodDecorator = <T>(
  target: Object,
  propertyKey: string | symbol,
  descriptor: TypedPropertyDescriptor<T>
) => TypedPropertyDescriptor<T> | void;

// 示例1:日志装饰器
function log(target: Object, propertyKey: string, descriptor: PropertyDescriptor) {
  const originalMethod = descriptor.value;
  
  descriptor.value = function(...args: any[]) {
    console.log(`调用方法: ${propertyKey}`);
    console.log(`参数: ${JSON.stringify(args)}`);
    const result = originalMethod.apply(this, args);
    console.log(`返回值: ${result}`);
    return result;
  };
  
  return descriptor;
}

class Calculator {
  @log
  add(x: number, y: number): number {
    return x + y;
  }
}

const calc = new Calculator();
calc.add(2, 3); // 输出日志信息

// 示例2:性能测量装饰器
function measureTime(target: Object, propertyKey: string, descriptor: PropertyDescriptor) {
  const originalMethod = descriptor.value;
  
  descriptor.value = function(...args: any[]) {
    const start = performance.now();
    const result = originalMethod.apply(this, args);
    const end = performance.now();
    console.log(`${propertyKey} 执行时间: ${end - start}ms`);
    return result;
  };
  
  return descriptor;
}

class DataProcessor {
  @measureTime
  processData(data: number[]): number[] {
    // 模拟数据处理
    return data.map(x => x * 2).filter(x => x > 10);
  }
}

// 示例3:缓存装饰器
function cache(target: Object, propertyKey: string, descriptor: PropertyDescriptor) {
  const originalMethod = descriptor.value;
  const cacheMap = new Map();
  
  descriptor.value = function(...args: any[]) {
    const key = JSON.stringify(args);
    
    if (cacheMap.has(key)) {
      console.log(`缓存命中: ${propertyKey}`);
      return cacheMap.get(key);
    }
    
    const result = originalMethod.apply(this, args);
    cacheMap.set(key, result);
    return result;
  };
  
  return descriptor;
}

class MathUtils {
  @cache
  expensiveCalculation(x: number, y: number): number {
    // 模拟耗时计算
    let result = 0;
    for (let i = 0; i < 1000000; i++) {
      result += Math.sin(x) * Math.cos(y);
    }
    return result;
  }
}

属性装饰器

// 属性装饰器签名
type PropertyDecorator = (
  target: Object,
  propertyKey: string | symbol
) => void;

// 示例1:只读属性装饰器
function readonly(target: Object, propertyKey: string) {
  const descriptor: PropertyDescriptor = {
    writable: false,
    configurable: false
  };
  
  Object.defineProperty(target, propertyKey, descriptor);
}

class User {
  @readonly
  id: number;
  
  name: string;
  
  constructor(id: number, name: string) {
    this.id = id;
    this.name = name;
  }
}

const user = new User(1, "Alice");
// user.id = 2; // Error: Cannot assign to read only property 'id'

// 示例2:格式验证装饰器
function validateEmail(target: Object, propertyKey: string) {
  let value: string;
  
  const getter = function() {
    return value;
  };
  
  const setter = function(newVal: string) {
    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    if (!emailRegex.test(newVal)) {
      throw new Error(`Invalid email: ${newVal}`);
    }
    value = newVal;
  };
  
  Object.defineProperty(target, propertyKey, {
    get: getter,
    set: setter,
    enumerable: true,
    configurable: true
  });
}

class Contact {
  @validateEmail
  email: string;
  
  constructor(email: string) {
    this.email = email;
  }
}

// const contact = new Contact("invalid-email"); // Error: Invalid email

// 示例3:监听属性变化
function watch(target: Object, propertyKey: string) {
  const privateKey = `_${propertyKey}`;
  
  Object.defineProperty(target, privateKey, {
    writable: true,
    enumerable: false,
    configurable: true
  });
  
  const getter = function() {
    return (this as any)[privateKey];
  };
  
  const setter = function(newVal: any) {
    const oldVal = (this as any)[privateKey];
    (this as any)[privateKey] = newVal;
    
    if (oldVal !== newVal) {
      console.log(`属性 ${propertyKey}${oldVal} 变为 ${newVal}`);
      // 可以触发自定义事件
      if ((this as any).onPropertyChange) {
        (this as any).onPropertyChange(propertyKey, oldVal, newVal);
      }
    }
  };
  
  Object.defineProperty(target, propertyKey, {
    get: getter,
    set: setter,
    enumerable: true,
    configurable: true
  });
}

class Observable {
  @watch
  value: number = 0;
  
  onPropertyChange?: (key: string, oldVal: any, newVal: any) => void;
}

参数装饰器

// 参数装饰器签名
type ParameterDecorator = (
  target: Object,
  propertyKey: string | symbol | undefined,
  parameterIndex: number
) => void;

// 示例1:参数验证装饰器
function validateParam(
  target: Object,
  propertyKey: string | symbol,
  parameterIndex: number
) {
  const existingValidations: number[] = 
    Reflect.getMetadata("validations", target, propertyKey) || [];
  
  existingValidations.push(parameterIndex);
  Reflect.defineMetadata("validations", existingValidations, target, propertyKey);
}

// 示例2:依赖注入装饰器
function inject(token: string) {
  return function(
    target: Object,
    propertyKey: string | symbol | undefined,
    parameterIndex: number
  ) {
    const existingInjections: { index: number; token: string }[] = 
      Reflect.getMetadata("injections", target) || [];
    
    existingInjections.push({ index: parameterIndex, token });
    Reflect.defineMetadata("injections", existingInjections, target);
  };
}

// 结合方法装饰器使用
function validateMethod(target: Object, propertyKey: string, descriptor: PropertyDescriptor) {
  const originalMethod = descriptor.value;
  const validations = Reflect.getMetadata("validations", target, propertyKey) || [];
  
  descriptor.value = function(...args: any[]) {
    for (const index of validations) {
      if (args[index] === undefined || args[index] === null) {
        throw new Error(`参数 ${index} 不能为空`);
      }
    }
    return originalMethod.apply(this, args);
  };
  
  return descriptor;
}

class Service {
  @validateMethod
  process(
    @validateParam id: number,
    @validateParam name: string,
    optional?: string
  ) {
    console.log(`处理: ${id} - ${name}`);
  }
}

装饰器元数据

  • 需要 reflect-metadata 库支持
  • tsconfig.json 中启用 "emitDecoratorMetadata": true
  • 可以存储和检索装饰器的元数据
import "reflect-metadata";

// 定义元数据键
const METADATA_KEY = {
  designType: "design:type",
  designParamTypes: "design:paramtypes",
  designReturnType: "design:returntype"
};

// 使用元数据
function logTypes(target: Object, propertyKey: string) {
  // 获取属性类型
  const propType = Reflect.getMetadata(METADATA_KEY.designType, target, propertyKey);
  console.log(`${propertyKey} 类型:`, propType);
  
  // 获取方法参数类型
  const paramTypes = Reflect.getMetadata(METADATA_KEY.designParamTypes, target, propertyKey);
  if (paramTypes) {
    console.log(`${propertyKey} 参数类型:`, paramTypes);
  }
  
  // 获取方法返回值类型
  const returnType = Reflect.getMetadata(METADATA_KEY.designReturnType, target, propertyKey);
  if (returnType) {
    console.log(`${propertyKey} 返回值类型:`, returnType);
  }
}

class Example {
  @logTypes
  property: string = "";
  
  @logTypes
  method(param1: number, param2: string): boolean {
    return true;
  }
}

// 输出:
// property 类型: [Function: String]
// method 参数类型: [ [Function: Number], [Function: String] ]
// method 返回值类型: [Function: Boolean]

装饰器执行顺序

  1. 参数装饰器(方法或构造函数)
  2. 方法/属性/访问器装饰器
  3. 类装饰器

实际应用场景

  1. 日志记录:记录方法的调用和参数
  2. 性能监控:测量方法执行时间
  3. 缓存:缓存方法结果
  4. 验证:验证参数和属性
  5. 依赖注入:自动注入依赖
  6. 权限控制:检查用户权限
  7. 事务管理:管理数据库事务

补充说明

  • 装饰器是实验性特性,未来可能变化
  • 在 Angular、NestJS 等框架中广泛使用
  • 合理使用装饰器可以提高代码的可维护性和可扩展性
  • 注意装饰器的执行顺序和性能影响

十、配置与最佳实践

10. TypeScript 项目配置

问题:TypeScript tsconfig.json 的配置项?TypeScript 编译选项?TypeScript 严格模式的配置?TypeScript 路径映射的配置?TypeScript 声明文件的配置?

答案

tsconfig.json 结构

{
  "compilerOptions": {
    /* 基本选项 */
    "target": "es5",
    "module": "commonjs",
    "lib": ["es6", "dom"],
    
    /* 严格模式选项 */
    "strict": true,
    
    /* 模块解析选项 */
    "moduleResolution": "node",
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"]
    },
    
    /* 输出选项 */
    "outDir": "./dist",
    "sourceMap": true,
    "declaration": true,
    
    /* 其他选项 */
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}

重要编译选项详解

{
  "compilerOptions": {
    // 1. 目标版本
    "target": "es2015", // 编译目标:es3, es5, es6/es2015, es2016, es2017, es2018, es2019, es2020, esnext
    
    // 2. 模块系统
    "module": "commonjs", // 模块系统:commonjs, amd, system, umd, es6/es2015, es2020, esnext
    
    // 3. 库文件
    "lib": ["dom", "es6", "dom.iterable", "es2017"], // 包含的库文件
    
    // 4. 输出目录
    "outDir": "./dist", // 输出目录
    "rootDir": "./src", // 输入目录
    
    // 5. 源映射
    "sourceMap": true, // 生成 sourceMap
    "inlineSourceMap": false, // 内联 sourceMap
    
    // 6. 声明文件
    "declaration": true, // 生成 .d.ts 声明文件
    "declarationDir": "./types", // 声明文件输出目录
    "declarationMap": true, // 为声明文件生成 sourceMap
    
    // 7. 严格模式(建议开启)
    "strict": true, // 启用所有严格类型检查选项
    
    // 严格模式子选项
    "noImplicitAny": true, // 禁止隐式 any 类型
    "strictNullChecks": true, // 严格的 null 检查
    "strictFunctionTypes": true, // 严格的函数类型检查
    "strictBindCallApply": true, // 严格的 bind/call/apply 检查
    "strictPropertyInitialization": true, // 严格的属性初始化检查
    "noImplicitThis": true, // 禁止隐式的 this
    "alwaysStrict": true, // 始终以严格模式解析
    
    // 8. 额外检查
    "noUnusedLocals": true, // 检查未使用的局部变量
    "noUnusedParameters": true, // 检查未使用的参数
    "noImplicitReturns": true, // 检查函数是否有隐式返回
    "noFallthroughCasesInSwitch": true, // 检查 switch 语句的 fallthrough
    
    // 9. 模块解析
    "moduleResolution": "node", // 模块解析策略:node, classic
    "baseUrl": ".", // 解析非相对模块的基础目录
    "paths": { // 路径映射
      "@/*": ["src/*"],
      "components/*": ["src/components/*"]
    },
    "rootDirs": ["src", "generated"], // 根目录列表
    
    // 10. 实验性功能
    "experimentalDecorators": true, // 启用装饰器
    "emitDecoratorMetadata": true, // 为装饰器生成元数据
    
    // 11. 高级选项
    "allowSyntheticDefaultImports": true, // 允许从没有默认导出的模块进行默认导入
    "esModuleInterop": true, // 启用 ES 模块互操作性
    "skipLibCheck": true, // 跳过库文件的类型检查
    "forceConsistentCasingInFileNames": true, // 强制文件名大小写一致
    "resolveJsonModule": true, // 允许导入 JSON 文件
    
    // 12. 输出选项
    "removeComments": false, // 移除注释
    "newLine": "lf", // 换行符:crlf, lf
    
    // 13. 工程引用
    "composite": true, // 启用工程引用
    "incremental": true // 启用增量编译
  },
  
  // 文件包含/排除
  "include": [
    "src/**/*.ts",
    "src/**/*.tsx",
    "tests/**/*.ts"
  ],
  "exclude": [
    "node_modules",
    "dist",
    "**/*.spec.ts",
    "**/*.test.ts"
  ],
  
  // 工程引用
  "references": [
    { "path": "./shared" },
    { "path": "./frontend" },
    { "path": "./backend" }
  ]
}

路径映射配置

{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      // 基本路径映射
      "@/*": ["src/*"],
      
      // 别名映射
      "components/*": ["src/components/*"],
      "utils/*": ["src/utils/*"],
      "types/*": ["src/types/*"],
      
      // 精确映射
      "lodash": ["node_modules/@types/lodash"],
      
      // 通配符映射
      "*": ["node_modules/@types/*", "types/*"]
    }
  }
}

声明文件配置

{
  "compilerOptions": {
    // 生成声明文件
    "declaration": true,
    "declarationDir": "./dist/types",
    "declarationMap": true,
    
    // 类型根目录
    "typeRoots": ["./node_modules/@types", "./src/types"],
    
    // 包含的类型
    "types": ["node", "jest", "react"]
  }
}

不同环境配置

// tsconfig.base.json - 基础配置
{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  }
}

// tsconfig.app.json - 应用配置
{
  "extends": "./tsconfig.base.json",
  "compilerOptions": {
    "outDir": "./dist",
    "rootDir": "./src",
    "lib": ["dom", "es6", "dom.iterable", "es2017"],
    "jsx": "react"
  },
  "include": ["src/**/*"],
  "exclude": ["**/*.test.ts", "**/*.spec.ts"]
}

// tsconfig.test.json - 测试配置
{
  "extends": "./tsconfig.base.json",
  "compilerOptions": {
    "outDir": "./dist-test",
    "rootDir": ".",
    "types": ["jest", "node"]
  },
  "include": ["src/**/*", "tests/**/*"]
}

最佳实践配置示例

{
  "compilerOptions": {
    /* 基础配置 */
    "target": "es2018",
    "module": "commonjs",
    "lib": ["es2018"],
    
    /* 严格模式(推荐) */
    "strict": true,
    
    /* 模块解析 */
    "moduleResolution": "node",
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"]
    },
    
    /* 输出配置 */
    "outDir": "dist",
    "rootDir": "src",
    
    /* 源码映射 */
    "sourceMap": true,
    
    /* 声明文件 */
    "declaration": true,
    "declarationDir": "dist/types",
    
    /* 互操作性 */
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    
    /* 额外检查 */
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noImplicitReturns": true,
    
    /* 实验性功能 */
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    
    /* 性能优化 */
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "incremental": true
  },
  
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts"]
}

补充说明

  • 始终开启严格模式("strict": true),提高代码质量
  • 使用路径映射(paths)简化导入路径
  • 为库项目生成声明文件(declaration
  • 根据项目需求选择合适的 targetmodule 配置
  • 使用工程引用(Project References)管理大型项目

总结

TypeScript涵盖了 79 道题目中的核心考点,通过系统化的讲解、详细代码示例和实用对比表格,帮助深入理解 TypeScript 的各个方面。从基础类型到高级特性,从装饰器到配置管理,全面覆盖了 TypeScript 开发所需的关键知识。

关键要点

  1. TypeScript 是 JavaScript 的超集,提供了静态类型检查等优势
  2. 类型系统是 TypeScript 的核心,包含基本类型、接口、泛型等
  3. 工具类型和高级类型操作是 TypeScript 的强项
  4. 装饰器提供了元编程能力,适合框架和库开发
  5. 合理配置 tsconfig.json 对项目开发至关重要

建议结合实际项目实践,深入掌握 TypeScript 的各种特性和最佳实践。