TypeScript开发实践

46 阅读7分钟

JavaScript 自有类型系统的问题

JavaScript 作为一门动态弱类型语言,其类型系统在灵活性和开发效率上有优势,但也存在显著问题,尤其在大型项目中。相比之下,TypeScript 作为静态类型超集,通过类型系统解决了这些问题。以下是详细对比:


JavaScript 类型系统的问题

  1. 动态类型(运行时类型检查)

    • 问题:类型错误只能在运行时暴露。
    • 示例
      function add(a, b) { return a + b; }
      add(1, "2"); // 返回 "12",静默失败!
      
    • 后果:线上运行时错误,调试成本高。
  2. 弱类型(隐式类型转换)

    • 问题:自动类型转换导致不可预测行为。
    • 示例
      "3" - 1;     // 2 (字符串转数字)
      "3" + 1;     // "31" (数字转字符串)
      [] == 0;      // true (数组转数字0)
      
    • 后果:逻辑错误难以追踪。
  3. 缺乏类型注解

    • 问题:代码可读性差,维护困难。
    • 示例
      function process(data) { /* data 的结构? */ }
      
    • 后果:需通过文档或代码回溯理解数据结构。
  4. 工具支持弱

    • 问题:IDE 无法提供准确的自动补全、重构和文档提示。
    • 后果:开发效率低,重构风险高。
  5. 大型项目维护难

    • 问题:修改代码时无法保证类型一致性。
    • 后果:牵一发而动全身,需大量手动测试。

TypeScript 的解决方案

  1. 静态类型检查

    • 优势:编译阶段捕获类型错误。
    • 示例
      function add(a: number, b: number): number {
          return a + b;
      }
      add(1, "2"); // 编译时报错!
      
  2. 强类型约束

    • 优势:禁止隐式类型转换(需显式类型断言)。
    • 示例
      let count: number = 5;
      count = "5"; // 编译错误
      
  3. 类型注解与推断

    • 优势:代码自文档化,提升可读性。
    • 示例
      interface User {
          id: number;
          name: string;
      }
      function getUser(id: number): User { /*...*/ }
      
  4. 强大的工具链

    • 优势:IDE 支持智能补全、重构和类型提示。
    • 示例(VSCode 中):
      const user: User = { id: 1, name: "Alice" };
      user. // 自动提示 id/name 属性
      
  5. 渐进式类型系统

    • 优势
      • 支持 any 类型绕过检查(兼容 JS 代码)。
      • 可逐步迁移,无需重写整个项目。

关键对比总结

特性JavaScriptTypeScript
类型检查时机运行时编译时
类型安全弱类型(隐式转换)强类型(显式转换)
类型注解不支持支持(接口/泛型/联合类型等)
工具链支持有限强大(代码提示、重构)
项目维护成本高(大型项目)低(类型即文档)
学习曲线中高(需掌握类型系统)
兼容性原生运行编译为 JS 后运行

何时选择 TypeScript?

  • 推荐场景
    • 中大型项目或团队协作。
    • 长期维护的项目。
    • 需要高可靠性的库/框架开发。
  • 慎用场景
    • 小型脚本或快速原型验证。
    • 已有大型 JS 项目迁移成本过高。

核心价值:TypeScript 通过静态类型将错误从运行时提前到编码阶段,显著提升代码健壮性和可维护性,是大型前端工程的基石。

TypeScript 基本语法

TypeScript 的基本语法在 JavaScript 基础上增加了类型标注类型系统特性,以下是核心语法详解:


一、基础类型标注

直接声明变量类型:

let isDone: boolean = false;    // 布尔
let count: number = 42;         // 数字
let name: string = "TypeScript";// 字符串

let list: number[] = [1, 2, 3];          // 数组(方式1)
let list2: Array<number> = [4, 5, 6];     // 数组(泛型语法)

let tuple: [string, number] = ["Alice", 30]; // 元组(固定类型+长度)

enum Color { Red, Green, Blue } // 枚举
let c: Color = Color.Green;

let notSure: any = "自由类型";   // 任意类型(慎用)
let nothing: void = undefined;  // 空值(常用于函数返回值)

二、函数类型标注

定义参数和返回值的类型:

// 完整写法
function add(a: number, b: number): number {
  return a + b;
}

// 箭头函数
const greet = (name: string): string => `Hello, ${name}!`;

// 可选参数(使用 ?)
function log(message: string, prefix?: string): void {
  console.log(prefix ? `${prefix}: ${message}` : message);
}

// 默认参数
function createUser(name: string, age: number = 18): User { ... }

三、接口(Interface)

定义对象结构:

interface User {
  id: number;
  name: string;
  email?: string;    // 可选属性
  readonly regDate: Date; // 只读属性
}

function printUser(user: User): void {
  console.log(`ID: ${user.id}, Name: ${user.name}`);
}

const alice: User = {
  id: 1,
  name: "Alice",
  regDate: new Date()
};
alice.regDate = new Date(); // 错误!regDate是只读的

四、类型别名(Type Aliases)

定义复杂类型:

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

type ID = number | string;  // 联合类型
type Callback = () => void; // 函数类型

五、类(Class)

增强 ES6 类的类型支持:

class Animal {
  private name: string;  // 私有属性
  protected age: number; // 受保护属性
  
  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }
  
  public move(distance: number = 0): void {
    console.log(`${this.name} moved ${distance}m`);
  }
}

class Dog extends Animal {
  bark(): void {
    console.log("Woof!");
    // this.name 不可访问(private)
    console.log(`Age: ${this.age}`); // 可访问(protected)
  }
}

六、泛型(Generics)

创建可复用的类型组件:

// 泛型函数
function identity<T>(arg: T): T {
  return arg;
}
identity<string>("TS"); // 显式指定类型
identity(42);          // 类型推断为 number

// 泛型接口
interface ApiResponse<T> {
  code: number;
  data: T;
}
const userRes: ApiResponse<User> = { ... };

七、类型断言(Type Assertion)

手动指定类型(绕过编译器检查):

const input = document.getElementById("input") as HTMLInputElement; // 方式1
const value = (<HTMLInputElement>input).value; // 方式2(JSX中禁用)

八、类型守卫(Type Guards)

缩小类型范围:

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

function printLength(value: string | number) {
  if (isString(value)) {
    console.log(value.length); // 此处 value 被识别为 string
  } else {
    console.log(value.toFixed(2)); // 此处为 number
  }
}

九、配置文件 tsconfig.json

关键配置示例:

{
  "compilerOptions": {
    "target": "ES6",        // 编译目标版本
    "module": "CommonJS",   // 模块系统
    "strict": true,         // 开启所有严格检查
    "outDir": "./dist",     // 输出目录
    "esModuleInterop": true // 改进模块兼容性
  },
  "include": ["src/**/*.ts"] // 编译范围
}

TypeScript 高级特性详解:枚举、接口与泛型

TypeScript 在 JavaScript 基础上增加了强大的类型系统,其中枚举(Enums)、**接口(Interfaces)泛型(Generics)**是三个核心高级特性,为开发者提供了更严格的类型控制和代码抽象能力。

一、枚举(Enums)

枚举是 TypeScript 独有的特性,用于定义一组命名的常量集合。

1. 数字枚举(默认)

// 默认从0开始自增
enum Direction {
  Up,     // 0
  Down,   // 1
  Left,   // 2
  Right   // 3
}

const move = (dir: Direction) => {
  switch(dir) {
    case Direction.Up:
      console.log("Moving up");
      break;
    case Direction.Down:
      console.log("Moving down");
      break;
    // ...
  }
}

move(Direction.Left); // 输出: "Moving left"

2. 字符串枚举

enum LogLevel {
  Info = "INFO",
  Warn = "WARNING",
  Error = "ERROR"
}

function log(message: string, level: LogLevel) {
  console.log(`[${level}] ${message}`);
}

log("File not found", LogLevel.Error);

3. 常量枚举(编译优化)

const enum HttpStatus {
  OK = 200,
  Created = 201,
  BadRequest = 400,
  NotFound = 404
}

// 编译后会被内联替换为常量值
const status = HttpStatus.OK; // 编译为: const status = 200;

4. 异构枚举(混合类型)

enum BooleanLike {
  No = 0,
  Yes = "YES"
}

5. 枚举的高级用法

// 位标志枚举
enum FilePermission {
  None = 0,
  Read = 1 << 0,    // 1
  Write = 1 << 1,   // 2
  Execute = 1 << 2, // 4
  All = Read | Write | Execute // 7
}

const myPermission: FilePermission = FilePermission.Read | FilePermission.Write;

// 检查权限
if (myPermission & FilePermission.Read) {
  console.log("Has read permission");
}

// 添加权限
myPermission |= FilePermission.Execute;

二、接口(Interfaces)

接口定义对象的结构和契约,是 TypeScript 的核心特性之一。

1. 基本对象接口

interface User {
  id: number;
  name: string;
  email: string;
  age?: number; // 可选属性
  readonly createdAt: Date; // 只读属性
}

const user: User = {
  id: 1,
  name: "Alice",
  email: "alice@example.com",
  createdAt: new Date()
};

// user.createdAt = new Date(); // 错误!只读属性不能修改

2. 函数类型接口

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

const mySearch: SearchFunc = (src, sub) => {
  return src.search(sub) > -1;
};

3. 可索引类型接口

// 数组类型
interface StringArray {
  [index: number]: string;
}

const myArray: StringArray = ["Alice", "Bob"];

// 字典类型
interface NumberDictionary {
  [key: string]: number;
  length: number; // 必须匹配索引类型
  // name: string; // 错误!不符合索引类型
}

4. 类实现接口

interface ClockInterface {
  currentTime: Date;
  setTime(d: Date): void;
}

class Clock implements ClockInterface {
  currentTime: Date = new Date();
  
  setTime(d: Date) {
    this.currentTime = d;
  }
}

5. 接口继承

interface Shape {
  color: string;
}

interface PenStroke {
  penWidth: number;
}

interface Square extends Shape, PenStroke {
  sideLength: number;
}

const square: Square = {
  color: "blue",
  penWidth: 5.0,
  sideLength: 10
};

6. 高级接口技巧

// 动态属性接口
interface Config {
  [propName: string]: any;
}

const config: Config = {
  name: "App",
  version: 1.0,
  debug: true
};

// 函数和属性混合接口
interface Counter {
  (start: number): string;
  interval: number;
  reset(): void;
}

function getCounter(): Counter {
  const counter = ((start: number) => {}) as Counter;
  counter.interval = 5;
  counter.reset = () => {};
  return counter;
}

const c = getCounter();
c(10);
c.reset();
c.interval = 5.0;

三、泛型(Generics)

泛型提供了一种创建可复用组件的方式,这些组件可以支持多种类型。

1. 泛型函数

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

const output = identity<string>("Hello");
const output2 = identity(42); // 类型推断

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

function loggingIdentity<T extends Lengthwise>(arg: T): T {
  console.log(arg.length); // 现在知道有length属性
  return arg;
}

loggingIdentity("hello"); // OK
loggingIdentity({ length: 10, value: 3 }); // OK
// loggingIdentity(3); // 错误!数字没有length属性

2. 泛型接口

// 基本泛型接口
interface KeyValuePair<K, V> {
  key: K;
  value: V;
}

const pair1: KeyValuePair<number, string> = { key: 1, value: "One" };
const pair2: KeyValuePair<string, boolean> = { key: "isValid", value: true };

// 函数泛型接口
interface Transformer<T, U> {
  (input: T): U;
}

const toUpperCase: Transformer<string, string> = (str) => str.toUpperCase();
const toNumber: Transformer<string, number> = (str) => parseFloat(str);

3. 泛型类

class GenericNumber<T> {
  zeroValue: T;
  add: (x: T, y: T) => T;
  
  constructor(zeroValue: T, add: (x: T, y: T) => T) {
    this.zeroValue = zeroValue;
    this.add = add;
  }
}

const myNumber = new GenericNumber<number>(0, (x, y) => x + y);
console.log(myNumber.add(5, 10)); // 15

const myString = new GenericNumber<string>("", (x, y) => x + y);
console.log(myString.add("Hello, ", "World!")); // "Hello, World!"

4. 高级泛型技巧

// 使用类型参数进行约束
function getProperty<T, K extends keyof T>(obj: T, key: K) {
  return obj[key];
}

const obj = { a: 1, b: 2, c: 3 };
getProperty(obj, "a"); // OK
// getProperty(obj, "d"); // 错误!"d"不是obj的key

// 工厂函数模式
class Animal {
  name: string;
}

class Bee extends Animal {
  honey: boolean = true;
}

function createInstance<A extends Animal>(c: new () => A): A {
  return new c();
}

const bee = createInstance(Bee);
console.log(bee.honey); // true

// 条件类型
type NonNullable<T> = T extends null | undefined ? never : T;
type Result = NonNullable<string | null | undefined>; // string

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

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

type User = {
  name: string;
  age: number;
};

type ReadonlyUser = Readonly<User>;
type PartialUser = Partial<User>;

5. 泛型工具类型

TypeScript 内置了多个实用泛型类型:

// 所有属性变为可选
type PartialUser = Partial<User>;

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

// 从类型中选取一组属性
type NameOnly = Pick<User, 'name'>; // { name: string }

// 构造具有指定属性的类型
type UserWithoutAge = Omit<User, 'age'>; // { name: string }

// 从T中排除可以赋值给U的类型
type T0 = Exclude<"a" | "b" | "c", "a">; // "b" | "c"

// 从T中提取可以赋值给U的类型
type T1 = Extract<"a" | "b" | "c", "a" | "f">; // "a"

// 排除null和undefined
type T2 = NonNullable<string | null | undefined>; // string

// 获取函数返回类型
type Return = ReturnType<() => string>; // string

// 获取构造函数参数类型
type Params = ConstructorParameters<typeof Error>; // [message?: string]

// 获取实例类型
type Instance = InstanceType<typeof Error>; // Error

四、组合使用高级特性

1. 泛型枚举

enum Status {
  Active = "ACTIVE",
  Inactive = "INACTIVE"
}

interface Entity<T> {
  id: T;
  status: Status;
}

const user: Entity<number> = {
  id: 1,
  status: Status.Active
};

const product: Entity<string> = {
  id: "prod-001",
  status: Status.Inactive
};

2. 接口与泛型结合

interface Repository<T> {
  get(id: number): Promise<T>;
  save(entity: T): Promise<void>;
  delete(id: number): Promise<boolean>;
}

class UserRepository implements Repository<User> {
  async get(id: number): Promise<User> {
    // 模拟数据库获取
    return { id, name: `User ${id}`, email: `user${id}@example.com` };
  }
  
  async save(user: User): Promise<void> {
    // 保存逻辑
  }
  
  async delete(id: number): Promise<boolean> {
    // 删除逻辑
    return true;
  }
}

3. 高级类型操作

// 条件类型 + 映射类型
type Nullable<T> = { [K in keyof T]: T[K] | null };

type UserNullable = Nullable<User>;
// 等同于: { name: string | null; age: number | null }

// 递归类型
type DeepReadonly<T> = {
  readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K];
};

const config: DeepReadonly<{ db: { host: string; port: number } }> = {
  db: { host: "localhost", port: 5432 }
};

// config.db.host = "remote"; // 错误!只读属性

总结

TypeScript 的高级特性提供了强大的类型抽象能力:

  1. 枚举(Enums)

    • 定义命名常量集合
    • 支持数字、字符串和异构类型
    • 使用常量枚举优化编译输出
  2. 接口(Interfaces)

    • 定义对象结构和契约
    • 支持函数、可索引和类实现
    • 通过继承创建复杂类型层次
  3. 泛型(Generics)

    • 创建可重用类型组件
    • 支持函数、接口和类
    • 结合约束、条件类型和映射类型实现高级模式

掌握这些高级特性可以:

  • 大幅提高代码类型安全性
  • 增强代码抽象和复用能力
  • 提供更智能的IDE支持
  • 改善大型项目的可维护性
  • 实现复杂类型操作和模式

这些特性共同构成了 TypeScript 强大的类型系统,使其成为构建大型、可维护前端应用的首选语言。

TypeScript 类型检查机制:类型推断与类型兼容性

TypeScript 的类型系统是其核心价值所在,理解类型推断和类型兼容性机制是掌握 TypeScript 的关键。下面我将深入解析这些机制:

一、类型推断 (Type Inference)

TypeScript 能在不显式指定类型的情况下自动推断类型:

1. 基础类型推断

let x = 3;          // 推断为 number
let name = "Alice"; // 推断为 string
let arr = [1, 2, 3]; // 推断为 number[]

2. 上下文类型推断

// 事件处理函数中 e 被推断为 MouseEvent
window.addEventListener("click", (e) => {
  console.log(e.clientX); // e 被正确推断为 MouseEvent
});

3. 函数返回类型推断

function add(a: number, b: number) {
  return a + b; // 返回类型被推断为 number
}

const users = [{ name: "Alice" }, { name: "Bob" }];
// user 被推断为 { name: string }
users.map(user => user.name.toUpperCase()); 

4. 最佳通用类型推断

const values = [1, "two", true]; // 推断为 (number | string | boolean)[]

5. const 断言

const point = { x: 10, y: 20 } as const;
// point 类型为 { readonly x: 10; readonly y: 20; }

二、类型兼容性 (Type Compatibility)

TypeScript 使用结构化类型系统(structural typing),基于类型结构而非名称判断兼容性。

1. 对象类型兼容性

interface Named {
  name: string;
}

class Person {
  name: string;
  age: number;
}

let p: Named;
p = new Person(); // OK,因为 Person 包含 name 属性

// 多余属性检查(fresh object literal检查)
p = { name: "Alice", age: 30 }; 
// 错误:对象字面量只能指定已知属性

2. 函数类型兼容性

函数兼容性基于:

  • 参数数量(目标函数参数 ≤ 源函数参数)
  • 参数类型(逆变)
  • 返回类型(协变)
// 参数兼容性(逆变)
let handler1 = (arg: string) => {};
let handler2 = (arg: string | number) => {};

handler1 = handler2; // 错误:handler2 参数更宽泛
handler2 = handler1; // OK:handler1 参数更具体

// 返回值兼容性(协变)
let factory1 = (): string | number => "hello";
let factory2 = (): string => "world";

factory1 = factory2; // OK:返回值更具体
factory2 = factory1; // 错误:返回值更宽泛

3. 类类型兼容性

class Animal {
  feet: number = 4;
}

class Size {
  feet: number = 0;
}

let a: Animal = new Animal();
let s: Size = new Size();

a = s; // OK:结构相同
s = a; // OK:结构相同

4. 泛型兼容性

// 空泛型接口总是兼容
interface Empty<T> {}
let x: Empty<number> = {};
let y: Empty<string> = {};
x = y; // OK

// 非空泛型接口取决于类型参数
interface NotEmpty<T> {
  data: T;
}
let num: NotEmpty<number> = { data: 1 };
let str: NotEmpty<string> = { data: "hello" };
num = str; // 错误:number 和 string 不兼容

三、高级类型兼容性规则

1. 可选参数与剩余参数

type Func1 = (a: number, b?: number) => void;
type Func2 = (a: number, ...rest: number[]) => void;

let f1: Func1 = (a, b) => {};
let f2: Func2 = (a, ...rest) => {};

f1 = f2; // OK
f2 = f1; // OK

2. 枚举兼容性

enum Status { Ready, Waiting }
enum Color { Red, Blue, Green }

let s = Status.Ready;
s = Color.Red; // 错误:不同枚举类型不兼容
s = 1; // OK:数字枚举与数字兼容

3. 私有成员影响兼容性

class PrivateClass {
  private secret = 123;
}

class PublicClass {
  public value = 456;
}

let privateInst = new PrivateClass();
let publicInst = new PublicClass();

privateInst = publicInst; // 错误:私有成员影响兼容性

四、类型保护与缩小 (Type Narrowing)

1. typeof 类型保护

function padLeft(value: string, padding: string | number) {
  if (typeof padding === "number") {
    return Array(padding + 1).join(" ") + value; // padding 被识别为 number
  }
  return padding + value; // padding 被识别为 string
}

2. instanceof 类型保护

class Bird {
  fly() {}
}
class Fish {
  swim() {}
}

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

3. in 操作符类型保护

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

interface Square {
  kind: "square";
  sideLength: number;
}

type Shape = Circle | Square;

function getArea(shape: Shape) {
  if ("radius" in shape) {
    return Math.PI * shape.radius ** 2; // shape 被识别为 Circle
  }
  return shape.sideLength ** 2; // shape 被识别为 Square
}

4. 自定义类型保护

function isCircle(shape: Shape): shape is Circle {
  return shape.kind === "circle";
}

function processShape(shape: Shape) {
  if (isCircle(shape)) {
    console.log("Radius:", shape.radius);
  } else {
    console.log("Side length:", shape.sideLength);
  }
}

五、strict 模式下的类型检查

tsconfig.json 中启用严格模式:

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

严格模式包含:

  • noImplicitAny:禁止隐式 any 类型
  • strictNullChecks:严格 null 检查
  • strictFunctionTypes:函数参数逆变检查
  • strictBindCallApply:严格 bind/call/apply 检查
  • strictPropertyInitialization:类属性初始化检查

strictNullChecks 示例

function getUser(id: string): User | undefined {
  // 可能返回 undefined
}

const user = getUser("123");
console.log(user.name); // 错误:user 可能为 undefined

// 正确写法
if (user) {
  console.log(user.name);
}

六、类型兼容性高级技巧

1. 条件类型中的类型兼容

type IsString<T> = T extends string ? true : false;

type A = IsString<"hello">; // true
type B = IsString<42>;      // false

2. 映射类型与兼容性

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

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

const partialPoint: Partial<Point> = { x: 10 }; // OK
const fullPoint: Point = partialPoint;          // 错误:缺少 y 属性

3. 函数重载与兼容性

function createDate(timestamp: number): Date;
function createDate(year: number, month: number, day: number): Date;
function createDate(overload: number, month?: number, day?: number): Date {
  if (month !== undefined && day !== undefined) {
    return new Date(overload, month, day);
  }
  return new Date(overload);
}

const d1 = createDate(1640995200000);     // OK
const d2 = createDate(2023, 0, 1);        // OK
const d3 = createDate(2023, 0);           // 错误:没有匹配的重载

总结

TypeScript 的类型检查机制的核心要点:

  1. 类型推断

    • 自动推断变量、函数返回类型
    • 上下文推断减少类型注解
    • const 断言创建字面量类型
  2. 类型兼容性

    • 基于结构而非名称(鸭子类型)
    • 对象:包含所有必需属性
    • 函数:参数逆变 + 返回值协变
    • 类:仅比较实例成员(静态成员和构造函数不影响)
  3. 类型保护

    • 使用 typeof、instanceof、in 缩小类型范围
    • 自定义类型保护函数
    • 可辨识联合类型
  4. 严格模式

    • 提供更安全的类型检查
    • 特别是 strictNullChecks 防止空值错误

理解这些机制能帮助开发者:

  • 编写更安全的代码,减少运行时错误
  • 利用类型推断减少冗余的类型注解
  • 设计更灵活的类型结构
  • 理解类型系统的工作原理,避免常见陷阱

掌握类型推断和兼容性规则是成为 TypeScript 高级开发者的关键一步,它能让你在类型安全和开发效率之间找到最佳平衡点。

TypeScript 类型保护机制详解

类型保护是 TypeScript 的核心特性,用于在特定代码块中缩小变量的类型范围,使编译器能更精确地推断类型。以下是 TypeScript 类型保护机制的全面解析:

一、内置类型保护方法

1. typeof 类型保护

用于判断基本类型(string, number, boolean, symbol

function padLeft(value: string, padding: string | number) {
  if (typeof padding === "number") {
    return Array(padding + 1).join(" ") + value;
    // padding 被识别为 number
  }
  return padding + value;
  // padding 被识别为 string
}

2. instanceof 类型保护

用于检查类实例

class Bird {
  fly() {}
}
class Fish {
  swim() {}
}

function move(pet: Bird | Fish) {
  if (pet instanceof Bird) {
    pet.fly(); // 此处 pet 被识别为 Bird
  } else {
    pet.swim(); // 此处 pet 被识别为 Fish
  }
}

3. in 操作符类型保护

检查对象是否包含特定属性

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

interface Square {
  kind: "square";
  sideLength: number;
}

type Shape = Circle | Square;

function getArea(shape: Shape) {
  if ("radius" in shape) {
    return Math.PI * shape.radius ** 2; // shape 被识别为 Circle
  }
  return shape.sideLength ** 2; // shape 被识别为 Square
}

二、自定义类型保护函数

1. 基本自定义类型保护

使用 parameter is Type 语法

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

function process(input: string | number) {
  if (isString(input)) {
    console.log(input.toUpperCase()); // input 被识别为 string
  } else {
    console.log(input.toFixed(2)); // input 被识别为 number
  }
}

2. 复杂对象类型保护

interface Admin {
  role: "admin";
  permissions: string[];
}

interface User {
  role: "user";
  isActive: boolean;
}

type Account = Admin | User;

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

function showDashboard(account: Account) {
  if (isAdmin(account)) {
    console.log("Admin permissions:", account.permissions);
    // account 被识别为 Admin
  } else {
    console.log("User status:", account.isActive);
    // account 被识别为 User
  }
}

三、可辨识联合(Discriminated Unions)

当联合类型有共同的可辨识属性时,TypeScript 会自动进行类型保护:

type NetworkState =
  | { state: "loading" }
  | { state: "success"; response: string }
  | { state: "error"; code: number };

function handleState(state: NetworkState) {
  switch (state.state) {
    case "loading":
      console.log("Loading..."); // state 被识别为 { state: "loading" }
      break;
    case "success":
      console.log("Response:", state.response);
      // state 被识别为 { state: "success"; response: string }
      break;
    case "error":
      console.log("Error code:", state.code);
      // state 被识别为 { state: "error"; code: number }
      break;
  }
}

四、基于控制流的类型保护

TypeScript 会根据代码执行路径自动推断类型:

1. 真值检查

function printLength(str?: string) {
  if (str) {
    console.log(str.length); // str 被识别为 string (非空)
  } else {
    console.log("No string provided"); // str 被识别为 undefined
  }
}

2. 相等性检查

function example(x: string | number, y: string | boolean) {
  if (x === y) {
    console.log(x.toUpperCase()); // x 和 y 都被识别为 string
    console.log(y.toUpperCase());
  }
}

3. 类型断言函数

function assert(condition: any, msg?: string): asserts condition {
  if (!condition) {
    throw new Error(msg || "Assertion failed");
  }
}

function processValue(value: unknown) {
  assert(typeof value === "string", "Value must be a string");
  console.log(value.toUpperCase()); // value 被识别为 string
}

五、高级类型保护技术

1. 类型谓词与泛型结合

function isArrayOf<T>(
  value: unknown,
  check: (item: unknown) => item is T
): value is T[] {
  return Array.isArray(value) && value.every(check);
}

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

function processData(data: unknown) {
  if (isArrayOf(data, isNumber)) {
    const sum = data.reduce((a, b) => a + b, 0); // data 被识别为 number[]
    console.log("Sum:", sum);
  }
}

2. 映射类型保护

type PartialRecord<K extends keyof any, T> = {
  [P in K]?: T;
};

function hasRequiredProperties<T, K extends keyof T>(
  obj: PartialRecord<keyof T, unknown>,
  keys: K[]
): obj is Record<K, NonNullable<T[K]>> & Partial<Omit<T, K>> {
  return keys.every(key => obj[key] !== null && obj[key] !== undefined);
}

interface User {
  id: number;
  name: string;
  email?: string;
}

function saveUser(user: Partial<User>) {
  if (hasRequiredProperties(user, ["id", "name"])) {
    // user 被识别为 { id: number; name: string; email?: string }
    db.save(user);
  }
}

3. 模板字面量类型保护

type CSSUnit = "px" | "em" | "rem" | "%";

function isCSSValue(value: string): value is `${number}${CSSUnit}` {
  return /^\d+(\.\d+)?(px|em|rem|%)$/.test(value);
}

function setWidth(value: string | number) {
  if (isCSSValue(value)) {
    // value 被识别为 `${number}${CSSUnit}`
    element.style.width = value;
  } else if (typeof value === "number") {
    element.style.width = `${value}px`;
  }
}

六、类型保护的最佳实践

  1. 优先使用可辨识联合

    // 优于
    if ('radius' in shape) { ... }
    
    // 推荐使用
    if (shape.kind === 'circle') { ... }
    
  2. 避免过度使用类型断言

    // 不推荐
    const name = (user as any).name;
    
    // 推荐使用类型保护
    if ('name' in user) { ... }
    
  3. 为复杂检查创建可重用类型保护

    // 创建可重用的类型保护
    function isHTMLElement(node: Node): node is HTMLElement {
      return node instanceof HTMLElement;
    }
    
    // 在多个地方使用
    if (isHTMLElement(target)) {
      target.classList.add('active');
    }
    
  4. 结合可选链和空值合并

    function getUserEmail(user?: User): string {
      // 类型保护 + 可选链 + 空值合并
      return user?.email ?? "no-email@example.com";
    }
    

七、类型保护在严格模式下的重要性

启用 strictNullChecks 后,类型保护成为处理可能为 nullundefined 值的必备技术:

interface ApiResponse {
  data?: {
    user?: {
      name: string;
      age: number;
    }
  }
}

function processResponse(response: ApiResponse) {
  // 没有类型保护 - 错误!
  // console.log(response.data.user.name); 
  
  // 使用可选链
  console.log(response.data?.user?.name);
  
  // 使用类型保护
  if (response.data?.user) {
    const { name, age } = response.data.user;
    console.log(`${name}, ${age} years old`);
  }
}

总结

TypeScript 类型保护机制提供了多种方式在代码执行路径中缩小变量类型范围:

技术适用场景优点
typeof基本类型检查简单快速
instanceof类实例检查面向对象友好
in 操作符对象属性检查直观明了
自定义类型保护函数复杂类型检查可重用、可测试
可辨识联合联合类型区分模式匹配、自动类型推断
控制流分析基于代码路径的类型推断自动、无需额外语法
类型断言函数确保特定条件提供运行时检查

核心价值

  1. 提高代码安全性:避免访问不存在的属性
  2. 增强代码可读性:明确表达类型预期
  3. 改善开发体验:获得精确的IDE提示
  4. 减少类型断言:提供更优雅的类型处理方案
  5. 支持严格模式:正确处理可选值和空值

掌握类型保护技术是编写健壮、可维护 TypeScript 代码的关键,尤其在大型项目中能显著减少运行时错误和维护成本。

TypeScript 高级特性-交叉与索引类型

交叉类型和索引类型是 TypeScript 类型系统中强大的高级特性,它们提供了处理复杂类型关系的灵活工具。下面我将深入解析这两大特性及其高级应用。

一、交叉类型(Intersection Types)

交叉类型使用 & 操作符组合多个类型,创建一个包含所有类型特性的新类型。

1. 基本用法

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

interface Employee {
  company: string;
  employeeId: number;
}

type EmployeePerson = Person & Employee;

const john: EmployeePerson = {
  name: "John",
  age: 30,
  company: "TechCorp",
  employeeId: 12345
};

2. 类型合并机制

  • 同名基础类型:相同属性会合并为交集

    type A = { id: string };
    type B = { id: number };
    type C = A & B; // { id: never } 因为 string & number 是空集
    
  • 同名对象类型:递归合并

    type Address = {
      street: string;
      city: string;
    };
    
    type Contact = {
      address: {
        city: string;
        zip: string;
      };
    };
    
    type UserInfo = Address & Contact;
    /*
    等价于:
    {
      street: string;
      city: string; // Address.city 和 Contact.address.city 合并
      address: {
        city: string;
        zip: string;
      };
    }
    */
    

3. 高级应用场景

混入模式(Mixin Pattern)
// 可记录功能
type Loggable = {
  log: (message: string) => void;
};

// 可序列化功能
type Serializable = {
  serialize(): string;
};

// 创建可记录和序列化的对象
function createEntity<T>(base: T): T & Loggable & Serializable {
  return {
    ...base,
    log(message) {
      console.log(message);
    },
    serialize() {
      return JSON.stringify(this);
    }
  };
}

const user = createEntity({ name: "Alice", age: 30 });
user.log("User created"); // OK
console.log(user.serialize()); // OK
组合配置选项
type BaseConfig = {
  apiUrl: string;
  timeout: number;
};

type AuthConfig = {
  token: string;
  refreshToken?: string;
};

type LogConfig = {
  logLevel: "debug" | "info" | "error";
};

type AppConfig = BaseConfig & AuthConfig & LogConfig;

const config: AppConfig = {
  apiUrl: "https://api.example.com",
  timeout: 5000,
  token: "abc123",
  logLevel: "info"
};

二、索引类型(Index Types)

索引类型允许我们基于对象的键动态操作类型。

1. 核心操作符

keyof - 获取所有键的联合类型
interface User {
  id: number;
  name: string;
  email: string;
  age: number;
}

type UserKeys = keyof User; // "id" | "name" | "email" | "age"
T[K] - 索引访问类型
type UserIdType = User["id"]; // number
type UserValueTypes = User[keyof User]; // number | string

2. 高级索引类型技术

动态属性访问
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

const user: User = { id: 1, name: "Alice", email: "a@example.com", age: 30 };
const name = getProperty(user, "name"); // string
const age = getProperty(user, "age"); // number
映射类型(Mapped Types)
// 所有属性变为只读
type ReadonlyUser = {
  readonly [K in keyof User]: User[K];
};

// 所有属性变为可选
type PartialUser = {
  [K in keyof User]?: User[K];
};

// 所有属性变为函数
type FunctionUser = {
  [K in keyof User]: (arg: User[K]) => void;
};

3. 索引签名(Index Signatures)

// 字符串索引签名
interface StringDictionary {
  [key: string]: string;
}

// 数字索引签名
interface NumberDictionary {
  [index: number]: string;
}

// 混合索引签名
interface HybridDictionary {
  [key: string]: string | number;
  [index: number]: string; // 数字索引返回值必须是字符串索引的子集
  name: string; // 必须符合索引签名
  // age: boolean; // 错误,不符合索引签名
}

三、交叉类型与索引类型的协同应用

1. 类型安全的对象扩展

function extend<T, U>(first: T, second: U): T & U {
  const result: Partial<T & U> = {};
  
  for (const prop in first) {
    if (first.hasOwnProperty(prop)) {
      (result as T)[prop] = first[prop];
    }
  }
  
  for (const prop in second) {
    if (second.hasOwnProperty(prop)) {
      (result as U)[prop] = second[prop];
    }
  }
  
  return result as T & U;
}

const person = { name: "Alice" };
const job = { title: "Engineer" };
const employee = extend(person, job); // { name: string; title: string }

2. 高级类型筛选

// 提取函数类型的属性
type FunctionProperties<T> = {
  [K in keyof T]: T[K] extends Function ? K : never;
}[keyof T];

// 提取非函数类型的属性
type NonFunctionProperties<T> = {
  [K in keyof T]: T[K] extends Function ? never : K;
}[keyof T];

interface UserModel {
  id: number;
  name: string;
  save: () => void;
  delete: () => boolean;
}

type UserFuncs = FunctionProperties<UserModel>; // "save" | "delete"
type UserData = NonFunctionProperties<UserModel>; // "id" | "name"

3. 条件类型与索引类型组合

// 深度只读
type DeepReadonly<T> = {
  readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K];
};

// 深度可选
type DeepPartial<T> = {
  [K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K];
};

interface Company {
  name: string;
  address: {
    street: string;
    city: string;
  };
}

type ReadonlyCompany = DeepReadonly<Company>;
/*
{
  readonly name: string;
  readonly address: {
    readonly street: string;
    readonly city: string;
  };
}
*/

4. 类型安全的 Redux Reducer

type Action<T extends string, P> = {
  type: T;
  payload: P;
};

type ActionMap = {
  SET_NAME: string;
  SET_AGE: number;
  ADD_TAG: string;
};

type Actions = {
  [K in keyof ActionMap]: Action<K, ActionMap[K]>;
}[keyof ActionMap];

function reducer(state: User, action: Actions): User {
  switch (action.type) {
    case "SET_NAME":
      return { ...state, name: action.payload }; // payload 是 string
    case "SET_AGE":
      return { ...state, age: action.payload }; // payload 是 number
    case "ADD_TAG":
      return { ...state, tags: [...(state.tags || []), action.payload] }; // payload 是 string
    default:
      return state;
  }
}

四、实用工具类型实现

1. Omit 类型实现

type MyOmit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;

interface Todo {
  title: string;
  description: string;
  completed: boolean;
}

type TodoPreview = MyOmit<Todo, "description">;
// { title: string; completed: boolean }

2. RequiredReadonly 增强版

type RequiredKeys<T> = {
  [K in keyof T]-?: object extends { [P in K]: T[K] } ? never : K;
}[keyof T];

type RequiredProperties<T> = Pick<T, RequiredKeys<T>>;
type OptionalProperties<T> = Omit<T, RequiredKeys<T>>;

interface Props {
  id: number;
  name?: string;
  age?: number;
}

type RequiredProps = RequiredProperties<Props>; // { id: number }
type OptionalProps = OptionalProperties<Props>; // { name?: string; age?: number }

五、高级模式与技巧

1. 动态接口扩展

interface BaseEntity {
  id: string;
  createdAt: Date;
}

type EntityMap = {
  user: { name: string };
  product: { price: number; sku: string };
  order: { total: number; items: string[] };
};

type Entity<T extends keyof EntityMap> = BaseEntity & EntityMap[T];

type UserEntity = Entity<"user">; // BaseEntity & { name: string }
type ProductEntity = Entity<"product">; // BaseEntity & { price: number; sku: string }

2. 精确类型的事件系统

type EventMap = {
  login: { user: string; timestamp: Date };
  logout: { user: string; reason: string };
  purchase: { user: string; amount: number; items: string[] };
};

type EventHandler<T> = (data: T) => void;

class EventEmitter {
  private events: {
    [K in keyof EventMap]?: EventHandler<EventMap[K]>[]
  } = {};

  on<K extends keyof EventMap>(event: K, handler: EventHandler<EventMap[K]>) {
    if (!this.events[event]) this.events[event] = [];
    this.events[event]!.push(handler);
  }

  emit<K extends keyof EventMap>(event: K, data: EventMap[K]) {
    this.events[event]?.forEach(handler => handler(data));
  }
}

const emitter = new EventEmitter();
emitter.on("login", data => {
  console.log(`User ${data.user} logged in at ${data.timestamp}`);
});

emitter.on("purchase", data => {
  console.log(`Purchase: $${data.amount} for ${data.items.length} items`);
});

// 类型错误:缺少 user 属性
// emitter.emit("logout", { reason: "timeout" });

// 正确
emitter.emit("logout", { user: "Alice", reason: "timeout" });

3. 数据库查询构建器

interface UserSchema {
  id: number;
  name: string;
  email: string;
  age: number;
  isActive: boolean;
}

type QueryBuilder<T> = {
  select<U extends keyof T>(...fields: U[]): QueryBuilder<Pick<T, U>>;
  where<K extends keyof T>(field: K, op: "=" | ">" | "<", value: T[K]): QueryBuilder<T>;
  limit(count: number): QueryBuilder<T>;
  execute(): Promise<T[]>;
};

function createQueryBuilder<T>(): QueryBuilder<T> {
  // 实现略...
  return {} as QueryBuilder<T>;
}

const userQuery = createQueryBuilder<UserSchema>()
  .select("id", "name", "email")
  .where("age", ">", 25)
  .where("isActive", "=", true)
  .limit(10)
  .execute();

// 返回类型为 Promise<{ id: number; name: string; email: string }[]>

六、最佳实践与注意事项

  1. 交叉类型陷阱

    • 避免过度使用交叉类型导致类型过于复杂
    • 注意同名属性的类型冲突会变成 never
  2. 索引类型优化

    • 对大型对象使用索引类型时注意性能影响
    • 使用 keyof 时考虑使用 ExtractExclude 过滤键
  3. 类型安全边界

    // 不安全:K 可能是任何字符串
    function unsafeGetValue<T>(obj: T, key: string): any {
      return obj[key as keyof T];
    }
    
    // 安全:限制 K 必须是 T 的键
    function safeGetValue<T, K extends keyof T>(obj: T, key: K): T[K] {
      return obj[key];
    }
    
  4. 递归深度限制

    • TypeScript 有递归深度限制(约50层)
    • 深层嵌套类型考虑使用迭代代替递归
  5. 索引签名与精确类型

    • 索引签名会放宽类型检查,优先使用精确类型
    • 需要精确类型时使用 Record 或映射类型

总结

交叉类型和索引类型是 TypeScript 类型系统中的高级特性,它们提供了:

特性核心能力典型应用场景
交叉类型合并多个类型特性混入模式、配置合并、功能组合
索引类型动态操作对象键和值类型类型安全访问、映射类型、高级工具
协同应用创建复杂但安全的类型系统ORM、状态管理、API 类型定义

掌握这些特性可以:

  • 创建高度可复用的类型工具
  • 实现复杂业务场景的类型安全
  • 减少冗余代码并提高类型表达能力
  • 增强大型项目的可维护性
  • 提升开发体验和代码质量

这些高级特性使 TypeScript 能够优雅地处理 JavaScript 的动态特性,同时提供静态类型系统的所有优势,是现代 TypeScript 高级开发的必备技能。

TypeScript 高级特性-映射类型

TypeScript 高级特性:映射类型深度解析

映射类型是 TypeScript 中最强大的高级特性之一,它允许你基于现有类型动态生成新类型。这种能力在创建实用工具类型、处理复杂数据结构和实现类型安全转换时至关重要。

一、映射类型基础

1. 核心概念

映射类型使用 in 操作符遍历键集合,并应用类型转换:

type MappedType<T> = {
  [P in keyof T]: Transformation<T[P]>;
};

2. 简单示例:将所有属性变为只读

type Readonly<T> = {
  readonly [P in keyof T]: T[P];
};

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

type ReadonlyPerson = Readonly<Person>;
/* 等价于:
{
  readonly name: string;
  readonly age: number;
}
*/

二、内置映射工具类型

TypeScript 提供了多个内置映射工具类型:

1. Partial<T> - 所有属性变为可选

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

interface User {
  id: number;
  name: string;
}

type PartialUser = Partial<User>;
// { id?: number; name?: string; }

2. Required<T> - 所有属性变为必选

type Required<T> = {
  [P in keyof T]-?: T[P]; // -? 移除可选修饰符
};

type RequiredUser = Required<PartialUser>;
// { id: number; name: string; }

3. Pick<T, K extends keyof T> - 选择特定属性

type Pick<T, K extends keyof T> = {
  [P in K]: T[P];
};

type NameOnly = Pick<User, 'name'>;
// { name: string; }

4. Record<K, T> - 创建键值映射

type Record<K extends keyof any, T> = {
  [P in K]: T;
};

type UserRoles = Record<'admin' | 'user' | 'guest', boolean>;
/* 等价于:
{
  admin: boolean;
  user: boolean;
  guest: boolean;
}
*/

三、高级映射技术

1. 键重映射(Key Remapping)

TypeScript 4.1+ 允许使用 as 子句重映射键名:

// 添加前缀
type AddPrefix<T, Prefix extends string> = {
  [K in keyof T as `${Prefix}${Capitalize<string & K>}`]: T[K];
};

interface Config {
  host: string;
  port: number;
}

type PrefixedConfig = AddPrefix<Config, 'db'>;
/* 等价于:
{
  dbHost: string;
  dbPort: number;
}
*/

2. 条件键过滤

// 过滤函数类型属性
type NonFunctionKeys<T> = {
  [K in keyof T as T[K] extends Function ? never : K]: T[K];
};

class Example {
  id: number = 1;
  save() {}
}

type DataProps = NonFunctionKeys<Example>;
// { id: number; }

3. 值类型转换

// 将所有属性变为Promise
type Promisify<T> = {
  [K in keyof T]: Promise<T[K]>;
};

type AsyncUser = Promisify<User>;
/* 等价于:
{
  id: Promise<number>;
  name: Promise<string>;
}
*/

四、递归映射类型

1. 深度只读

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

interface Company {
  name: string;
  address: {
    street: string;
    city: string;
  };
}

type ReadonlyCompany = DeepReadonly<Company>;
/* 等价于:
{
  readonly name: string;
  readonly address: {
    readonly street: string;
    readonly city: string;
  };
}
*/

2. 深度可选

type DeepPartial<T> = {
  [K in keyof T]?: T[K] extends object 
    ? DeepPartial<T[K]> 
    : T[K];
};

五、实用映射模式

1. 类型安全的 CSS 工具

type CSSProperties = {
  color?: string;
  fontSize?: number | string;
  margin?: number | string;
  // ...更多属性
};

type ResponsiveProps<T> = {
  base: T;
  sm?: T;
  md?: T;
  lg?: T;
  xl?: T;
};

type ResponsiveCSS = ResponsiveProps<CSSProperties>;

const styles: ResponsiveCSS = {
  base: { color: 'black', fontSize: 16 },
  md: { fontSize: 18 },
  xl: { margin: '2rem' }
};

2. API 响应标准化

type ApiResponse<T> = {
  data: T;
  status: number;
  timestamp: Date;
};

type Normalized<T> = {
  [K in keyof T]: T[K] extends Array<infer U>
    ? { ids: number[]; entities: Record<number, Normalized<U>> }
    : T[K] extends object ? Normalized<T[K]> : T[K];
};

type User = {
  id: number;
  name: string;
  posts: Post[];
};

type Post = {
  id: number;
  title: string;
};

type NormalizedUser = Normalized<User>;
/* 等价于:
{
  id: number;
  name: string;
  posts: {
    ids: number[];
    entities: Record<number, {
      id: number;
      title: string;
    }>;
  };
}
*/

3. Redux Action 创建器

type ActionMap = {
  LOGIN: { username: string; token: string };
  LOGOUT: undefined;
  ADD_TO_CART: { productId: string; quantity: number };
};

type ActionCreators = {
  [K in keyof ActionMap]: ActionMap[K] extends undefined
    ? () => { type: K }
    : (payload: ActionMap[K]) => { type: K; payload: ActionMap[K] };
};

// 自动生成的创建器
const actions: ActionCreators = {
  LOGIN: (payload) => ({ type: 'LOGIN', payload }),
  LOGOUT: () => ({ type: 'LOGOUT' }),
  ADD_TO_CART: (payload) => ({ type: 'ADD_TO_CART', payload })
};

六、模板字面量映射

TypeScript 4.1+ 支持模板字面量类型与映射结合:

1. 事件处理器映射

type EventMap = {
  click: MouseEvent;
  change: Event;
  keydown: KeyboardEvent;
};

type EventHandlers = {
  [K in keyof EventMap as `on${Capitalize<K>}`]: (event: EventMap[K]) => void;
};

/* 等价于:
{
  onClick: (event: MouseEvent) => void;
  onChange: (event: Event) => void;
  onKeydown: (event: KeyboardEvent) => void;
}
*/

2. 双向绑定映射

type ModelProps<T> = {
  [K in keyof T]: T[K];
};

type BindingProps<T> = {
  [K in keyof T as `bind${Capitalize<string & K>}`]: {
    value: T[K];
    onChange: (value: T[K]) => void;
  };
};

type FormComponentProps<T> = ModelProps<T> & BindingProps<T>;

interface FormState {
  name: string;
  age: number;
}

type FormProps = FormComponentProps<FormState>;
/* 等价于:
{
  name: string;
  age: number;
  bindName: { value: string; onChange: (value: string) => void };
  bindAge: { value: number; onChange: (value: number) => void };
}
*/

七、高级技巧与最佳实践

1. 性能优化

对于大型类型,考虑使用条件类型避免深度递归:

// 优化版DeepReadonly
type DeepReadonly<T> = T extends (infer U)[]
  ? ReadonlyArray<DeepReadonly<U>>
  : T extends Function
  ? T
  : T extends object
  ? { readonly [K in keyof T]: DeepReadonly<T[K]> }
  : T;

2. 保留原始修饰符

type KeepModifiers<T> = {
  [K in keyof T]: T[K];
} & {};

3. 映射枚举类型

enum UserRole {
  Admin = 'ADMIN',
  Editor = 'EDITOR',
  Viewer = 'VIEWER'
}

type RolePermissions = {
  [K in UserRole]: {
    canEdit: boolean;
    canDelete: boolean;
  };
};

const permissions: RolePermissions = {
  [UserRole.Admin]: { canEdit: true, canDelete: true },
  [UserRole.Editor]: { canEdit: true, canDelete: false },
  [UserRole.Viewer]: { canEdit: false, canDelete: false }
};

4. 联合类型映射

type UnionToIntersection<U> = 
  (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) 
    ? I 
    : never;

type StringKey<T> = T extends string ? T : never;

type KeysToObject<T extends string, V = any> = {
  [K in T]: V;
};

type MyKeys = 'id' | 'name' | 'email';
type KeyObject = KeysToObject<MyKeys, string>;
/* 等价于:
{
  id: string;
  name: string;
  email: string;
}
*/

八、映射类型限制与解决方案

1. 递归深度限制

TypeScript 有递归深度限制(约50层),解决方案:

// 扁平化处理
type Flatten<T> = T extends Array<infer U> ? Flatten<U> : T;

type DeepArray = number[][][][][];
type FlatArray = Flatten<DeepArray>; // number[]

2. 保留可选属性信息

type PreserveOptional<T> = {
  [K in keyof T]: T[K];
} & {};

3. 处理索引签名

type WithIndex<T> = T & {
  [key: string]: any;
};

type Enhanced = WithIndex<{ id: number }>;
const obj: Enhanced = { id: 1, extra: 'value' }; // 允许额外属性

九、映射类型应用场景

  1. API 响应标准化:统一处理服务器响应结构
  2. 表单处理:自动生成表单状态和验证类型
  3. 状态管理:创建类型安全的 Redux 或 Vuex 状态
  4. ORM/ODM:映射数据库结构到类型系统
  5. 主题系统:管理 CSS 变量和主题配置
  6. 国际化:类型安全的翻译键管理
  7. 配置管理:处理环境变量和配置对象

总结

映射类型是 TypeScript 类型系统的核心高级特性,它提供了:

能力描述应用场景
属性转换修改属性特性(只读、可选等)创建实用工具类型
键重映射修改键名(添加前缀、后缀等)自动生成事件处理器
递归映射深度处理嵌套对象复杂数据标准化
条件映射基于条件过滤或转换API响应处理
模板字面量动态生成键名双向数据绑定

最佳实践建议

  1. 优先使用内置工具类型(Partial, Pick, Record)
  2. 对于复杂转换,创建可重用的映射类型工具
  3. 注意递归深度限制,优化大型类型
  4. 结合条件类型和类型推断实现高级模式
  5. 使用模板字面量类型增强键映射表达能力

掌握映射类型可以显著提升你的 TypeScript 能力,使你能够创建更灵活、更安全的类型系统,特别是在处理复杂数据结构和实现高级编程模式时。

TypeScript 声明文件

声明文件(.d.ts)是 TypeScript 生态系统的核心组成部分,它为 JavaScript 库提供类型信息,使 TypeScript 能够理解 JavaScript 代码的结构和行为。

一、声明文件基础

1. 声明文件的作用

  • 为 JavaScript 代码提供类型信息
  • 支持现有 JavaScript 库的 TypeScript 开发
  • 提供智能提示和类型检查
  • 描述模块结构和全局变量

2. 声明文件类型

类型位置示例
全局声明文件项目根目录global.d.ts
模块声明文件与模块同名jquery.d.ts
@types 包node_modules/@types@types/react
自动生成声明文件TS 编译输出dist/index.d.ts

二、核心声明语法

1. 变量声明

declare const VERSION: string;
declare let DEBUG: boolean;

2. 函数声明

declare function greet(name: string): void;
declare function fetchData(url: string): Promise<any>;

3. 类声明

declare class User {
  constructor(name: string, age: number);
  getName(): string;
  static createAnonymous(): User;
}

4. 接口声明

declare interface ApiResponse<T> {
  data: T;
  status: number;
  error?: string;
}

5. 命名空间(全局库)

declare namespace MyLib {
  function doSomething(): void;
  const version: string;
  
  namespace Utilities {
    function formatDate(date: Date): string;
  }
}

6. 模块声明(CommonJS/AMD/UMD)

declare module "my-module" {
  export function calculate(a: number, b: number): number;
  export const PI: number;
}

7. 类型别名

declare type UUID = string;
declare type Callback<T> = (data: T) => void;

三、高级声明技术

1. 声明合并

// 接口合并
interface User {
  name: string;
}

interface User {
  age: number;
}

// 类与接口合并
declare class FormValidator {
  validate(): boolean;
}

interface FormValidator {
  setRules(rules: object): void;
}

2. 模块扩展

// 扩展第三方模块
declare module "moment" {
  export function customFormat(): string;
}

// 扩展全局对象
declare global {
  interface Window {
    myCustomProp: number;
  }
  
  namespace NodeJS {
    interface ProcessEnv {
      NODE_ENV: 'development' | 'production';
      API_KEY: string;
    }
  }
}

3. 条件类型声明

declare type MaybeArray<T> = T | T[];

declare type Unpacked<T> = 
  T extends (infer U)[] ? U :
  T extends (...args: any[]) => infer U ? U :
  T extends Promise<infer U> ? U :
  T;

4. 模板字面量类型

declare type EventName = `on${Capitalize<'click' | 'change' | 'submit'>}`;
// "onClick" | "onChange" | "onSubmit"

四、模块类型声明模式

1. CommonJS 模块

declare module "fs-extra" {
  import * as fs from "fs";
  
  export interface CopyOptions {
    overwrite?: boolean;
    preserveTimestamps?: boolean;
  }
  
  export function copy(src: string, dest: string, options?: CopyOptions): Promise<void>;
  
  export = fs;
}

2. ES 模块

declare module "geometry" {
  export interface Point {
    x: number;
    y: number;
  }
  
  export function distance(p1: Point, p2: Point): number;
  export default class Circle {
    constructor(center: Point, radius: number);
    area(): number;
  }
}

3. UMD 模块(通用模块)

export as namespace myLibrary;

export = myLibrary;
declare function myLibrary(name: string): void;
declare namespace myLibrary {
  const version: string;
  interface Config {
    debug: boolean;
  }
}

五、复杂类型声明实战

1. React 组件声明

import * as React from 'react';

declare interface ButtonProps {
  primary?: boolean;
  size?: 'small' | 'medium' | 'large';
  onClick?: React.MouseEventHandler<HTMLButtonElement>;
}

declare const Button: React.ForwardRefExoticComponent<
  ButtonProps & React.RefAttributes<HTMLButtonElement>
>;

2. Redux Store 声明

import { Action, AnyAction, Reducer, Store } from 'redux';

declare module 'redux' {
  export interface Store<S = any, A extends Action = AnyAction> {
    asyncReducers: Record<string, Reducer>;
    injectReducer(key: string, reducer: Reducer): void;
  }
}

declare const configureStore: (initialState?: object) => Store;

3. Vue 3 Composition API 插件

import { App, Plugin } from 'vue';

declare interface AnalyticsPluginOptions {
  trackingId: string;
  debug?: boolean;
}

declare const AnalyticsPlugin: Plugin & {
  install(app: App, options: AnalyticsPluginOptions): void;
};

declare module '@vue/runtime-core' {
  interface ComponentCustomProperties {
    $analytics: {
      trackEvent(event: string, data?: object): void;
    };
  }
}

六、最佳实践与高级技巧

1. 类型安全的事件发射器

declare interface EventMap {
  login: { user: string; timestamp: Date };
  logout: { reason: string };
  purchase: { amount: number; items: string[] };
}

declare class EventEmitter {
  on<T extends keyof EventMap>(event: T, listener: (data: EventMap[T]) => void): void;
  emit<T extends keyof EventMap>(event: T, data: EventMap[T]): void;
}

2. 高级配置对象声明

declare type ConfigBuilder<T> = {
  [K in keyof T]-?: (value: T[K]) => ConfigBuilder<T>;
} & { build(): T };

declare function createConfig<T>(): ConfigBuilder<T>;

// 使用示例
const config = createConfig<{
  apiUrl: string;
  timeout: number;
  retries: number;
}>()
  .apiUrl("https://api.example.com")
  .timeout(5000)
  .retries(3)
  .build();

3. 声明文件测试技巧

// tests/declaration-tests.ts
import * as assert from 'assert';
import { SomeLibrary } from 'some-library';

// 测试类型是否符合预期
const testType: () => void = () => {
  const instance = new SomeLibrary();
  
  // 测试实例方法
  const result: number = instance.calculate(10, 20);
  assert.strictEqual(typeof result, 'number');
  
  // 测试静态属性
  const version: string = SomeLibrary.version;
  assert.strictEqual(typeof version, 'string');
  
  // 测试回调函数类型
  instance.on('event', (data: { value: number }) => {
    assert.strictEqual(typeof data.value, 'number');
  });
};

testType();

七、声明文件发布策略

1. 与 npm 包一起发布

package.json 中配置:

{
  "name": "my-package",
  "version": "1.0.0",
  "main": "dist/index.js",
  "types": "dist/index.d.ts",
  "files": ["dist"]
}

2. 独立发布到 DefinitelyTyped

  1. 创建目录结构:types/my-package/index.d.ts
  2. 添加 tsconfig.json 和测试文件
  3. 提交 PR 到 DefinitelyTyped

3. 自动生成声明文件

tsconfig.json 中配置:

{
  "compilerOptions": {
    "declaration": true,
    "declarationDir": "dist/types",
    "emitDeclarationOnly": true
  }
}

八、常见问题解决方案

1. 处理缺少的类型

// 临时解决方案
declare module 'legacy-module' {
  const content: any;
  export default content;
}

// 更安全的方案
declare module 'legacy-module' {
  interface LegacyAPI {
    doSomething(config: object): void;
    version: string;
  }
  
  const api: LegacyAPI;
  export default api;
}

2. 处理全局污染

// 使用模块化声明避免全局污染
export as namespace mySafeLib;

declare function mySafeLib(): void;

// 而不是
declare global {
  function myUnsafeLib(): void;
}

3. 处理类型冲突

// 使用类型重命名解决冲突
import { SomeType as OriginalType } from 'conflicting-module';

declare module 'my-module' {
  export type SomeType = OriginalType;
  export function useType(value: SomeType): void;
}

九、声明文件工具链

1. 生成声明文件

# 从 JS 生成初始声明文件
npx dts-gen --module my-module

# 从 TS 项目生成
tsc --declaration --emitDeclarationOnly

2. 验证声明文件

# 检查声明文件有效性
tsc --noEmit --project types/tsconfig.json

# 使用 dtslint 进行高级检查
npm install -g dtslint
dtslint types/my-package

3. 文档生成工具

# 使用 TypeDoc 生成文档
npx typedoc --out docs src/index.ts

十、总结:声明文件最佳实践

  1. 精确性原则

    • 优先使用具体类型而非 any
    • 为可选参数和属性添加 ?
    • 使用联合类型精确描述可能值
  2. 兼容性原则

    • 保持与 JavaScript 库版本的同步
    • 使用语义化版本控制声明文件
    • 提供向后兼容的类型声明
  3. 模块化原则

    • 优先使用 ES 模块语法
    • 避免不必要的全局声明
    • 合理组织大型声明文件
  4. 文档化原则

    /**
     * 用户认证服务
     * @param config - 认证配置
     */
    declare class AuthService {
      /**
       * 创建新用户
       * @param credentials - 用户凭证
       * @returns 创建的用户ID
       */
      createUser(credentials: {
        username: string;
        password: string;
        email: string;
      }): Promise<string>;
    }
    
  5. 性能原则

    • 避免深层嵌套的类型
    • 使用接口而非复杂内联类型
    • 合理使用类型别名简化复杂类型

声明文件是 TypeScript 生态系统的基石,掌握其高级用法能显著提升:

  • 第三方库的类型安全性
  • 代码编辑器的智能提示质量
  • 大型项目的可维护性
  • 团队协作的效率

通过结合 TypeScript 的高级类型特性,声明文件可以成为构建健壮类型系统的强大工具。