重新学习前端之TypeScript

TypeScript

一、类型系统(基础类型、联合类型、交叉类型、类型别名)

1. TypeScript 有哪些基础类型?

定义: TypeScript 基础类型是类型系统的基石,分为原始类型、对象类型和特殊类型三大类。

原理: TypeScript 在 JavaScript 的运行时类型之上添加了编译时静态类型层。类型注解在编译后会被擦除,不影响运行时行为。

typescript 复制代码
// 原始类型
let isDone: boolean = false;
let age: number = 25;
let name: string = "TypeScript";
let n: null = null;
let u: undefined = undefined;
let s: symbol = Symbol("id");
let big: bigint = 100n;

// 数组
let nums: number[] = [1, 2, 3];
let strs: Array<string> = ["a", "b"];

// 元组
let tuple: [string, number] = ["hello", 42];

// 枚举
enum Color { Red, Green, Blue }

常见误区:

  • object 不包括原始类型,只表示非原始类型
  • number 包含整数和浮点数
  • 启用 strictNullChecks 后,nullundefined 只能赋给 anyunknown 或自身
  • bigint 是 ES2020 引入的大整数类型

2. 什么是联合类型?使用场景有哪些?

定义: 联合类型(Union Types)使用 | 分隔多个类型,表示值可以是其中任意一种类型。

原理: 联合类型创建多个类型的并集。在访问属性时,TypeScript 只允许访问所有成员共有的属性,需要通过类型守卫来缩小类型范围。

typescript 复制代码
// 基础联合类型
let id: string | number;
id = "abc";
id = 123;

// 使用场景:字面量联合
type Direction = "up" | "down" | "left" | "right";
function move(dir: Direction) { /* ... */ }

// 使用场景:多种对象类型
interface Cat { meow(): void; name: string; }
interface Dog { bark(): void; name: string; }

function greet(pet: Cat | Dog) {
  console.log(pet.name); // OK: 共有属性
  // pet.meow(); // Error: 不是共有属性
}

// 配合类型守卫使用
function handle(value: string | number) {
  if (typeof value === "string") {
    value.toUpperCase(); // 窄化为 string
  } else {
    value.toFixed(2);    // 窄化为 number
  }
}

常见误区:

  • 联合类型只能安全访问所有成员共有的属性和方法
  • 字面量联合类型比枚举更轻量,推荐使用
  • 函数参数的联合类型需要通过类型守卫区分处理

3. 什么是交叉类型?使用场景有哪些?

定义: 交叉类型(Intersection Types)使用 & 分隔多个类型,表示值必须同时具备所有成员类型的特性。

原理: 交叉类型将多个类型的成员合并为一个新类型,常用于对象类型的组合和扩展。

typescript 复制代码
// 基础交叉类型
interface Nameable { name: string; }
interface Ageable { age: number; }

type Person = Nameable & Ageable;
// 等价于 { name: string; age: number; }

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

// 使用场景:对象类型扩展
interface User { id: number; name: string; }
interface Admin { role: string; permissions: string[]; }

type AdminUser = User & Admin;
// { id: number; name: string; role: string; permissions: string[] }

// 使用场景:方法组合
interface Loggable { log(): void; }
interface Saveable { save(): void; }

type Persistable = Loggable & Saveable;

常见误区:

  • 交叉类型不等同于类的多继承
  • 如果两个类型有同名但不同类型的属性,交叉后该属性类型为 never
  • 交叉类型主要用于对象类型的组合

4. 联合类型与交叉类型的区别

对比分析:

| 维度 | 联合类型 A | B | 交叉类型 A & B | |------|-----------------|------------------| | 语义 | 取其一(OR) | 同时具备(AND) | | 符号 | | | & | | 赋值 | 满足任一类型即可 | 必须满足所有类型 | | 属性访问 | 只能访问共有成员 | 可访问所有成员 | | 类比 | 集合的并集 | 集合的交集 | | 典型场景 | 函数参数多态、字面量枚举 | 对象组合、Mixin 模式 |

typescript 复制代码
// 联合类型:可能是多种类型之一
type Result = { ok: true; data: string } | { ok: false; error: string };
function process(r: Result) {
  if (r.ok) {
    r.data; // 窄化后访问
  }
}

// 交叉类型:同时具备多种类型的特性
type WithId = { id: string };
type WithTimestamp = { createdAt: Date; updatedAt: Date };
type Entity = WithId & WithTimestamp;

选择策略:

  • 函数参数接受多种可能类型 → 联合类型
  • 对象需要组合多个接口/类型 → 交叉类型
  • 字面量类型限制取值范围 → 联合类型
  • 在现有类型上扩展属性 → 交叉类型

5. TypeScript 类型系统的特性有哪些?

核心特性:

  1. 静态类型检查: 编译时验证类型,而非运行时
  2. 结构化类型(Duck Typing): 兼容性基于结构而非名称
  3. 类型推断: 编译器自动推断类型
  4. 类型守卫: 运行时判断 + 编译时窄化
  5. 泛型支持: 类型参数化
  6. 联合/交叉类型: 灵活的类型组合
  7. 装饰器: 元编程能力
  8. 映射类型/条件类型: 高级类型变换
  9. 声明合并: 同名声明自动合并
  10. 类型别名: type 关键字创建别名
typescript 复制代码
// 结构化类型示例:基于结构兼容
interface A { x: number; y: number; }
interface B { x: number; y: number; }
let a: A = { x: 1, y: 2 };
let b: B = a; // OK: 结构相同

6. 数据类型与存储机制(堆栈区别)

原理: JavaScript 中的数据类型分为值类型(原始类型)和引用类型,分别存储在栈内存和堆内存中。

特性 栈内存(值类型) 堆内存(引用类型)
存储内容 实际值 内存地址引用
大小 固定 动态可变
访问速度 较慢
垃圾回收 自动清理 GC 管理
赋值行为 值拷贝 地址拷贝
数据类型 boolean/number/string/null/undefined/symbol/bigint object/array/function/class
typescript 复制代码
// 值类型:栈存储
let a = 10;
let b = a;  // 值拷贝
b = 20;
console.log(a); // 10,互不影响

// 引用类型:堆存储
let obj1 = { x: 1 };
let obj2 = obj1; // 地址拷贝
obj2.x = 2;
console.log(obj1.x); // 2,互相影响

7. 类型别名的定义方式

定义: 类型别名使用 type 关键字给任何类型创建一个新名称。

typescript 复制代码
// 基本类型别名
type ID = string | number;

// 对象类型别名
type User = {
  id: number;
  name: string;
  email?: string;
};

// 联合类型别名
type Status = "pending" | "success" | "error";

// 泛型别名
type Result<T> = { ok: true; data: T } | { ok: false; error: string };

// 交叉类型别名
type Admin = User & { role: string };

// 映射类型别名
type Optional<T> = { [P in keyof T]?: T[P] };

8. type 与 interface 的区别

对比分析:

维度 interface type
声明合并 支持(同名自动合并) 不支持
扩展语法 extends & 交叉类型
支持范围 对象/函数/数组/类 所有类型(联合/交叉/元组/字面量/映射等)
类实现 implements 支持 不支持直接 implements
重复声明 允许(自动合并) 不允许(重复定义报错)
计算属性 不支持 支持映射类型
typescript 复制代码
// interface 声明合并
interface User { id: number; }
interface User { name: string; }
// 合并为 { id: number; name: string }

// type 不能合并
type User = { id: number; }
// type User = { name: string; } // Error: 重复定义

// interface 只能定义对象形状
interface Point { x: number; y: number; }

// type 可以定义任何类型
type Point = { x: number; y: number };
type ID = string | number;
type Pair = [number, number];
type Callback<T> = (data: T) => void;

选择策略:

  • 定义对象形状、供类实现、需要声明合并时 → 使用 interface
  • 定义联合/交叉/元组/字面量类型、需要映射类型时 → 使用 type
  • 团队协作建议统一约定

二、接口(Interface)与类型别名

9. TypeScript 接口的定义与用途

定义: 接口(Interface)用于定义对象的结构契约,规定对象必须具有哪些属性和方法。

typescript 复制代码
// 基本接口
interface User {
  id: number;
  name: string;
  email: string;
}

// 函数类型接口
interface SearchFunc {
  (source: string, subString: string): boolean;
}
let search: SearchFunc = (src, sub) => src.includes(sub);

// 数组类型接口
interface StringArray {
  [index: number]: string;
}
let arr: StringArray = ["a", "b"];

// 类实现接口
interface ClockInterface {
  currentTime: Date;
  setTime(d: Date): void;
}
class Clock implements ClockInterface {
  currentTime: Date = new Date();
  setTime(d: Date) { this.currentTime = d; }
}

接口用途:

  • 定义对象结构,提供类型安全
  • 作为类实现的契约
  • 函数参数类型约束
  • API 响应结构定义
  • 模块间通信的类型约定

10. 接口可以包含哪些成员?

typescript 复制代码
interface FullInterface {
  // 必需属性
  id: number;
  name: string;

  // 可选属性
  email?: string;

  // 只读属性
  readonly createdAt: Date;

  // 方法
  greet(): void;
  add(a: number, b: number): number;

  // 索引签名
  [key: string]: any;

  // 调用签名(接口自身可调用)
  (x: number): string;

  // 构造签名(创建实例)
  new (x: number): object;
}

11. 接口继承与扩展

原理: 接口通过 extends 关键字继承其他接口,实现类型复用和组合。

typescript 复制代码
// 单继承
interface Animal { name: string; }
interface Dog extends Animal { bark(): void; }

// 多继承
interface Flyable { fly(): void; }
interface Swimmable { swim(): void; }
interface Duck extends Animal, Flyable, Swimmable {}

// 接口继承类型别名
type Point = { x: number; y: number };
interface Point3D extends Point { z: number; }

12. 接口合并(声明合并)

定义: 同名接口会自动合并,这是 TypeScript 声明合并的一种形式。

typescript 复制代码
interface Document {
  title: string;
}
interface Document {
  author: string;
}

// 合并后等价于
// interface Document { title: string; author: string; }

let doc: Document = { title: "TS Guide", author: "Alice" };

合并规则:

  • 同名非函数成员:类型必须一致,否则报错
  • 同名函数成员:形成重载
  • 命名空间与类/函数/枚举可以合并

13. 索引签名 / 可索引类型

typescript 复制代码
// 字符串索引签名
interface StringDict {
  [key: string]: number;
}
let dict: StringDict = { a: 1, b: 2 };

// 数字索引签名
interface StringArray {
  [index: number]: string;
}
let arr: StringArray = ["a", "b"];

// 联合索引
interface MixedDict {
  [key: string]: number | string;
}

// 注意:索引签名会限制其他属性的类型
interface BadDict {
  [key: string]: number;
  name: string; // Error: string 不兼容 number
}

14. 可选属性与只读属性

typescript 复制代码
// 可选属性
interface Config {
  host: string;
  port?: number;
  timeout?: number;
}

// 只读属性
interface Point {
  readonly x: number;
  readonly y: number;
}
let p: Point = { x: 1, y: 2 };
// p.x = 3; // Error: 只读属性不可修改

// 只读数组
let arr: ReadonlyArray<number> = [1, 2, 3];
// arr.push(4); // Error

三、泛型(Generics)

15. 什么是泛型?泛型的用途

定义: 泛型允许在定义函数、接口或类时不指定具体类型,而是用类型参数代替,在使用时再指定具体类型。

原理: 泛型通过类型参数将类型抽象化,实现类型安全的代码复用,避免使用 any 丢失类型信息。

typescript 复制代码
// 泛型函数
function identity<T>(arg: T): T {
  return arg;
}
let output1 = identity<string>("hello");
let output2 = identity(42); // 推断为 number

// 泛型接口
interface Result<T> {
  code: number;
  data: T;
  message: string;
}

// 泛型类
class Stack<T> {
  private items: T[] = [];
  push(item: T) { this.items.push(item); }
  pop(): T | undefined { return this.items.pop(); }
}

// 多泛型参数
function pair<T, U>(a: T, b: U): [T, U] {
  return [a, b];
}
let p = pair("hello", 42); // [string, number]

用途:

  1. 组件/函数复用,同一逻辑适配多种类型
  2. 保持类型安全,避免使用 any
  3. 确保参数间类型关系的一致性
  4. 构建可复用的工具函数和数据结构

16. 泛型约束

原理: 使用 extends 关键字限制泛型参数的范围,确保类型参数具有特定属性或结构。

typescript 复制代码
// 基本约束
interface Lengthwise { length: number; }
function loggingIdentity<T extends Lengthwise>(arg: T): T {
  console.log(arg.length);
  return arg;
}
loggingIdentity("hello"); // OK
// loggingIdentity(123); // Error: number 没有 length

// 使用 keyof 约束
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}
let obj = { a: 1, b: 2, c: 3 };
getProperty(obj, "a"); // OK
// getProperty(obj, "x"); // Error

// 构造函数约束
function createInstance<T extends { new(...args: any[]): {} }>(ctor: T): T {
  return new ctor();
}

17. 泛型默认值

typescript 复制代码
// 接口默认值
interface Response<T = any> {
  status: number;
  data: T;
}

// 函数默认值
function createArray<T = string>(length: number, value: T): T[] {
  return Array(length).fill(value);
}
let arr = createArray(3, "x"); // string[]

// 类型别名默认值
type Result<T = unknown> = { ok: true; data: T } | { ok: false; error: string };

四、装饰器(Decorators)

18. 什么是装饰器?装饰器的作用

定义: 装饰器是一种特殊类型的声明,能够被附加到类声明、方法、属性或参数上,用于修改类的行为或添加元数据。

原理: 装饰器本质上是函数,在类定义时被调用。需要在 tsconfig.json 中启用 experimentalDecorators

json 复制代码
{
  "compilerOptions": {
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true
  }
}

装饰器分类:

  1. 类装饰器
  2. 方法装饰器
  3. 属性装饰器
  4. 参数装饰器
  5. 访问器装饰器

19. 类装饰器

typescript 复制代码
// 基本类装饰器
function sealed(constructor: Function) {
  Object.seal(constructor);
  Object.seal(constructor.prototype);
}

@sealed
class Greeter {
  greeting: string;
  constructor(message: string) {
    this.greeting = message;
  }
}

// 返回新构造函数的装饰器
function classDecorator<T extends { new(...args: any[]): {} }>(constructor: T) {
  return class extends constructor {
    newProperty = "new property";
  };
}

@classDecorator
class MyClass {
  hello = "hello";
}

20. 方法装饰器

typescript 复制代码
function log(
  target: any,
  propertyKey: string,
  descriptor: PropertyDescriptor
) {
  const original = descriptor.value;
  descriptor.value = function (...args: any[]) {
    console.log(`调用 ${propertyKey},参数:${JSON.stringify(args)}`);
    const result = original.apply(this, args);
    console.log(`返回值:${JSON.stringify(result)}`);
    return result;
  };
  return descriptor;
}

class Calculator {
  @log
  add(a: number, b: number): number {
    return a + b;
  }
}

21. 属性装饰器

typescript 复制代码
function Min(min: number) {
  return function (target: any, propertyKey: string) {
    let value: number;
    Object.defineProperty(target, propertyKey, {
      get: () => value,
      set(newVal: number) {
        if (newVal < min) {
          throw new Error(`${propertyKey} 不能小于 ${min}`);
        }
        value = newVal;
      }
    });
  };
}

class Product {
  @Min(0)
  price: number;
}

22. 参数装饰器

typescript 复制代码
function Required(target: any, propertyKey: string, parameterIndex: number) {
  console.log(`类: ${target.constructor.name}, 方法: ${propertyKey}, 参数索引: ${parameterIndex}`);
}

class Service {
  login(@Required username: string, @Required password: string) {
    // ...
  }
}

23. 装饰器工厂与执行顺序

装饰器工厂: 返回装饰器函数的函数,可以传递参数。

typescript 复制代码
function Color(value: string) {
  return function (target: any) {
    target.color = value;
  };
}
@Color("red")
class Car {}

执行顺序:

  1. 属性装饰器 > 方法装饰器 > 方法参数装饰器 > 类装饰器
  2. 同一类型的装饰器:从右到左、从下到上执行
typescript 复制代码
function f(key: string) {
  console.log(`${key} evaluated`);
  return function (target: any, propertyKey: string) {
    console.log(`${key} called`);
  };
}

class Demo {
  @f("A")
  @f("B")
  method() {}
}
// 输出:
// A evaluated
// B evaluated
// B called
// A called

24. 装饰器应用场景

  1. 日志记录: 方法调用前后日志
  2. 权限校验: 路由/方法访问控制
  3. 缓存: 方法结果缓存(Memo 化)
  4. 依赖注入: Angular 框架核心机制
  5. 数据验证: DTO 字段验证(class-validator)
  6. 元数据反射: 存储类型信息
  7. Mixin 模式: 类功能组合

五、模块系统

25. TypeScript 模块

定义: TypeScript 模块分为外部模块(ES Modules / CommonJS)和内部模块(已废弃,使用命名空间替代)。

typescript 复制代码
// ES Module 导出 (math.ts)
export const PI = 3.14159;
export function add(a: number, b: number): number {
  return a + b;
}
export default class Calculator {}

// 导入 (app.ts)
import Calculator, { PI, add } from "./math";
import * as Math from "./math";

26. 模块导出与导入的语法

typescript 复制代码
// 命名导出
export { foo, bar };
export { foo as aliasFoo };

// 默认导出
export default function fn() {}
export default class MyClass {}

// 重新导出
export { foo } from "./module";
export * from "./module";

// 类型导入(TS 4.5+,编译时被移除)
import { type Foo } from "./module";

// CommonJS 导出
export = { foo: 1, bar: 2 };

// CommonJS 导入
import foo = require("./module");

27. CommonJS 与 ES6 模块的区别

维度 CommonJS ES6 模块
关键字 require() / module.exports import / export
加载时机 运行时加载 编译时静态分析
输出拷贝 值拷贝(浅拷贝) 值引用
循环依赖 可能返回不完整值 自动处理
Tree-shaking 不支持 支持
默认导出 export default
typescript 复制代码
// CommonJS
const express = require("express");
module.exports = { foo: 1 };

// ES Module
import express from "express";
export { foo: 1 };

28. 命名空间

typescript 复制代码
// 命名空间定义
namespace Validation {
  export interface Validator {
    isValid(s: string): boolean;
  }

  export class EmailValidator implements Validator {
    isValid(s: string): boolean {
      return s.includes("@");
    }
  }
}

// 使用
let validator: Validation.Validator = new Validation.EmailValidator();

// 跨文件命名空间
/// <reference path="validation.ts" />
namespace Validation {
  export class LengthValidator implements Validator { /* ... */ }
}

29. 模块与命名空间的区别

维度 模块 命名空间
文件关系 一个文件即一个模块 可在单个或多个文件中定义
作用域 文件级隔离 全局/嵌套作用域
加载方式 需要模块加载器 script 标签直接加载
推荐度 推荐使用 仅用于声明文件或遗留代码
打包支持 支持 tree-shaking 不支持

最佳实践: 优先使用模块,命名空间仅用于 .d.ts 声明文件。


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

定义: 声明文件用于描述 JavaScript 库的类型信息,供 TypeScript 编译器使用。

typescript 复制代码
// 全局声明
declare var jQuery: (selector: string) => any;

// 模块声明
declare module "my-module" {
  export function foo(): string;
  export const VERSION: string;
}

// 声明类
declare class MyClass {
  constructor(name: string);
  greet(): void;
}

// 声明函数重载
declare function format(fmt: string): string;
declare function format(fmt: string, ...args: any[]): string;

31. 声明合并

typescript 复制代码
// 接口合并
interface Box { width: number; }
interface Box { height: number; }
// 合并为 { width: number; height: number }

// 命名空间合并
namespace Animals { export class Cat {} }
namespace Animals { export class Dog {} }

// 函数与命名空间合并
function greet(name: string): void;
namespace greet {
  export const defaultGreeting = "Hello";
}

六、类型推断与类型断言

32. 类型推断的工作原理

定义: TypeScript 编译器在没有显式类型注解时,根据上下文自动推断出类型。

推断规则:

  1. 变量推断: 根据初始值推断
  2. 函数返回值推断: 根据 return 语句推断
  3. 上下文推断: 根据使用场景推断
  4. 泛型推断: 根据传入参数推断
typescript 复制代码
// 变量推断
let x = 3;        // 推断为 number
let y = "hello";  // 推断为 string

// 最佳通用类型推断
let arr = [1, null, "three"]; // (number | null | string)[]

// 上下文类型推断
window.onmousedown = function (mouseEvent) {
  // mouseEvent 推断为 MouseEvent
  console.log(mouseEvent.button);
};

// 函数返回值推断
function add(a: number, b: number) {
  return a + b; // 推断返回 number
}

// 泛型推断
function identity<T>(arg: T): T { return arg; }
let output = identity("hello"); // T 推断为 string

33. 什么是类型断言?使用场景

定义: 类型断言告诉编译器"相信我,这个值就是该类型",跳过编译器的类型检查。

语法:

typescript 复制代码
// as 语法(推荐,JSX 友好)
let someValue: unknown = "this is a string";
let strLength = (someValue as string).length;

// 尖括号语法(不能用于 JSX)
let strLength2 = (<string>someValue).length;

使用场景:

  1. DOM 元素类型转换
  2. 解析 JSON 数据
  3. 第三方库返回类型转换
  4. 测试 mock 数据

34. as 语法 / as string 语法的作用

typescript 复制代码
// DOM 元素类型转换
let elem = document.getElementById("app") as HTMLDivElement;

// 解析 JSON
let data = JSON.parse('{"name": "Alice"}') as { name: string };

// 常量断言(as const)
let x = "hello" as const;        // 类型为 "hello"
let y = [10, 20] as const;       // 类型为 readonly [10, 20]
let z = { text: "hello" } as const; // 类型为 { readonly text: "hello" }

35. 非空断言与双重断言

typescript 复制代码
// 非空断言操作符 !
function liveDangerously(x?: number | null) {
  console.log(x!.toFixed()); // 告诉编译器 x 不为 null/undefined
}

// 双重断言(绕过类型检查,慎用)
const handler = (event: Event) => {
  const mouseEvent = event as any as MouseEvent;
}

// satisfies 运算符(TS 4.9+)
const config = {
  host: "localhost",
  port: 3000
} satisfies Record<string, string | number>;
// config.host 保留精确类型

常见误区:

  • 类型断言不会改变运行时行为
  • 双重断言可能隐藏类型错误,应谨慎使用
  • as const 用于创建只读的字面量类型
  • ! 非空断言在运行时不会检查实际值

36. 什么是类型守卫?

定义: 类型守卫是运行时检查,用于确保变量在某个作用域内具有特定类型,编译器据此进行类型窄化。

类型守卫方式:

typescript 复制代码
// typeof 类型守卫
function padLeft(value: string, padding: string | number) {
  if (typeof padding === "number") {
    return " ".repeat(padding) + value; // 窄化为 number
  }
  return padding + value; // 窄化为 string
}

// instanceof 类型守卫
class Bird { fly() {} }
class Fish { swim() {} }

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

// in 操作符类型守卫
interface Bird { fly(): void; }
interface Fish { swim(): void; }

function handle(pet: Bird | Fish) {
  if ("swim" in pet) {
    pet.swim(); // 窄化为 Fish
  }
}

// 自定义类型守卫(类型谓词)
function isFish(pet: Bird | Fish): pet is Fish {
  return (pet as Fish).swim !== undefined;
}

if (isFish(pet)) {
  pet.swim(); // 窄化为 Fish
}

37. 字面量类型的使用

typescript 复制代码
// 字符串字面量类型
type Direction = "north" | "south" | "east" | "west";

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

// 布尔字面量类型
type TrueOnly = true;

// 组合使用(discriminated union)
type Event = { type: "click"; x: number; y: number }
           | { type: "keydown"; key: string };

function handleEvent(event: Event) {
  if (event.type === "click") {
    console.log(event.x, event.y); // 窄化
  }
}

七、工具类型(Utility Types)

38. TypeScript 工具类型概览

定义: 工具类型是 TypeScript 内置的泛型类型,用于常见的类型变换操作。


39. Partial<T> 工具类型的作用

作用: 将类型 T 的所有属性变为可选。

typescript 复制代码
interface User { id: number; name: string; email: string; }
type PartialUser = Partial<User>;
// { id?: number; name?: string; email?: string }

// 使用场景:更新接口
function updateUser(id: number, updates: Partial<User>) {
  // 只需要传递需要更新的字段
}

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

40. Required<T>、Readonly<T> 的用途

typescript 复制代码
// Required: 所有属性变为必选
interface Config { host?: string; port?: number; }
type RequiredConfig = Required<Config>;
// { host: string; port: number }

// Readonly: 所有属性变为只读
interface Todo { title: string; completed: boolean; }
type ReadonlyTodo = Readonly<Todo>;
// { readonly title: string; readonly completed: boolean }

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

41. Pick<T, K> 和 Omit<T, K> 的区别

typescript 复制代码
// Pick: 从 T 中选择指定属性
interface Todo { title: string; description: string; completed: boolean; }
type TodoPreview = Pick<Todo, "title" | "completed">;
// { title: string; completed: boolean }

// Omit: 从 T 中排除指定属性
type TodoInfo = Omit<Todo, "completed">;
// { title: string; description: string }

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

42. Exclude<T, U> 和 Extract<T, U> 的作用

typescript 复制代码
// Exclude: 从 T 中排除可分配给 U 的类型
type T1 = Exclude<"a" | "b" | "c", "a">; // "b" | "c"
type T2 = Exclude<string | number, number>; // string

// Extract: 从 T 中提取可分配给 U 的类型
type T3 = Extract<"a" | "b" | "c", "a" | "d">; // "a"
type T4 = Extract<string | number, number>; // number

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

43. NonNullable<T> 的作用

typescript 复制代码
type T1 = NonNullable<string | number | undefined>; // string | number
type T2 = NonNullable<string[] | null | undefined>; // string[]

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

44. ReturnType<T> 获取什么类型?

作用: 获取函数 T 的返回值类型。

typescript 复制代码
function getUser() {
  return { id: 1, name: "Alice" };
}
type User = ReturnType<typeof getUser>;
// { id: number; name: string }

// 箭头函数
type Fn = () => { x: number };
type R = ReturnType<Fn>; // { x: number }

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

45. Record<K, T>

作用: 构造一个对象类型,键类型为 K,值类型为 T

typescript 复制代码
type PageInfo = { title: string; url: string };
type Pages = "home" | "about" | "contact";

type SiteMap = Record<Pages, PageInfo>;
const sitemap: SiteMap = {
  home: { title: "Home", url: "/" },
  about: { title: "About", url: "/about" },
  contact: { title: "Contact", url: "/contact" }
};

46. Parameters<T>

作用: 获取函数 T 的参数类型组成的元组。

typescript 复制代码
function greet(name: string, age: number) {}
type GreetParams = Parameters<typeof greet>;
// [name: string, age: number]

// 常用于包装函数
function withLogging<T extends (...args: any[]) => any>(
  fn: T,
  name: string
): (...args: Parameters<T>) => ReturnType<T> {
  return (...args) => {
    console.log(`调用 ${name}`);
    return fn(...args);
  };
}

47. InstanceType<T>

作用: 获取构造函数类型 T 的实例类型。

typescript 复制代码
class C { x = 0; y = 0; }
type T1 = InstanceType<typeof C>; // C
type T2 = InstanceType<new () => { x: number }>; // { x: number }

48. ThisParameterType / OmitThisParameter / ThisType

typescript 复制代码
// ThisParameterType: 获取函数的 this 参数类型
function toHex(this: Number) { return this.toString(16); }
type ThisType = ThisParameterType<typeof toHex>; // Number

// OmitThisParameter: 移除函数的 this 参数
type NoThis = OmitThisParameter<typeof toHex>; // () => string

// ThisType: 用于对象字面量中指定 this 类型
interface ObjectMethods {
  methods: {
    hello: () => string;
  };
}
let obj: ObjectMethods & ThisType<{ name: string }> = {
  methods: {
    hello() { return `Hello, ${this.name}`; }
  }
};

49. 字符串工具类型

typescript 复制代码
type T1 = Uppercase<"hello">;    // "HELLO"
type T2 = Lowercase<"HELLO">;    // "hello"
type T3 = Capitalize<"hello">;   // "Hello"
type T4 = Uncapitalize<"Hello">; // "hello"

八、TypeScript 配置与编译

50. tsconfig.json 配置文件

定义: tsconfig.json 是 TypeScript 项目的配置文件,指定编译选项和包含的文件。

json 复制代码
{
  "compilerOptions": {
    "target": "ES2020",
    "module": "ESNext",
    "lib": ["ES2020", "DOM"],
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "moduleResolution": "node",
    "declaration": true,
    "sourceMap": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"],
  "extends": "./tsconfig.base.json"
}

51. tsconfig.json 的重要配置项

配置项 说明 常用值
target 编译目标 JS 版本 ES5/ES6/ES2020/ESNext
module 模块系统 CommonJS/ESNext/AMD
lib 包含的库类型声明 ES2020/DOM/DOM.Iterable
outDir 输出目录 ./dist
rootDir 源文件根目录 ./src
strict 启用所有严格检查 true
noImplicitAny 禁止隐式 any true
strictNullChecks 严格空值检查 true
esModuleInterop 兼容 CJS 和 ESM true
declaration 生成 .d.ts 文件 true
sourceMap 生成源映射 true
skipLibCheck 跳过声明文件检查 true
moduleResolution 模块解析策略 node/node16/bundler
resolveJsonModule 导入 JSON 文件 true

52. strict 模式的作用

定义: strict 模式启用所有严格类型检查选项,是最佳实践的起点。

等价于同时启用:

  • strictNullChecks - 严格空值检查
  • strictFunctionTypes - 严格函数类型检查
  • strictBindCallApply - 严格 bind/call/apply 检查
  • strictPropertyInitialization - 严格属性初始化检查
  • noImplicitThis - 禁止隐式 this 为 any
  • alwaysStrict - 始终使用严格模式
  • noImplicitAny - 禁止隐式 any
  • useUnknownInCatchVariables - catch 变量为 unknown

53. esModuleInterop 配置的意义

定义: 兼容 CommonJS 和 ES 模块的互操作。

typescript 复制代码
// esModuleInterop: true 时
import express from "express"; // OK: 允许默认导入 CommonJS

// esModuleInterop: false 时
import * as express from "express"; // 需要命名空间导入

原理: 为 CommonJS 模块创建命名空间对象,并允许 import default 语法。


54. target 与 module 选项

target: 指定编译目标 JavaScript 版本

说明
ES3 老旧浏览器(已不常用)
ES5 现代浏览器兼容
ES6/ES2015 支持 class、箭头函数等
ES2020+ 支持最新特性

module: 指定模块系统

说明
CommonJS Node.js 默认
ESNext ES 模块
AMD 异步模块定义
UMD 通用模块定义

55. include、exclude、files 与 extends

json 复制代码
{
  "files": ["src/index.ts", "src/app.ts"],       // 精确指定
  "include": ["src/**/*", "types/**/*"],          // 通配符匹配
  "exclude": ["node_modules", "dist", "**/*.test.ts"],  // 排除
  "extends": "./tsconfig.base.json"               // 继承配置
}

优先级: files > include > exclude


九、枚举

56. TypeScript 枚举

定义: 枚举是一组命名常量的集合,提供有意义的名称替代魔法数字。

typescript 复制代码
// 数字枚举
enum Direction {
  Up,       // 0
  Down,     // 1
  Left,     // 2
  Right     // 3
}

// 指定初始值
enum Direction2 {
  Up = 1,
  Down,     // 2
  Left,     // 3
  Right     // 4
}

57. 字符串枚举与异构枚举

typescript 复制代码
// 字符串枚举(推荐)
enum LogLevel {
  DEBUG = "DEBUG",
  INFO = "INFO",
  WARN = "WARN",
  ERROR = "ERROR"
}
// 无反向映射

// 异构枚举(不推荐)
enum Mixed {
  No = 0,
  Yes = "YES"
}

58. 反向映射与 const 枚举

typescript 复制代码
// 反向映射(仅数字枚举)
enum Direction { Up = 1, Down }
console.log(Direction.Up);       // 1
console.log(Direction[1]);       // "Up" 反向映射
console.log(Direction["Down"]);  // 2

// const 枚举(编译后内联,无运行时开销)
const enum Direction {
  Up, Down, Left, Right
}
let d = Direction.Up;
// 编译为: var d = 0 /* Up */;

59. 枚举合并

typescript 复制代码
enum Status { Active = "active" }
enum Status { Inactive = "inactive" }

// 合并为
// enum Status {
//   Active = "active",
//   Inactive = "inactive"
// }

十、特殊类型与高级类型

60. never、unknown、any、void 的区别

类型 含义 赋值能力 被赋值能力 使用场景
any 任意类型 任意 任意 禁用类型检查(避免使用)
unknown 任意类型(安全) any/unknown 任意 安全接收未知类型
never 永不存在的值 任意 never 穷尽检查、抛出异常
void 无返回值 undefined/null 函数无返回值
typescript 复制代码
// never: 穷尽检查
type Shape = { type: "circle"; r: number }
           | { type: "square"; side: number }
           | { type: "triangle"; base: number; height: number };

function area(shape: Shape): number {
  switch (shape.type) {
    case "circle": return Math.PI * shape.r ** 2;
    case "square": return shape.side ** 2;
    case "triangle": return 0.5 * shape.base * shape.height;
    default:
      const _exhaustive: never = shape;
      return _exhaustive;
  }
}

// unknown: 安全检查
function process(value: unknown) {
  // value.toUpperCase(); // Error
  if (typeof value === "string") {
    value.toUpperCase(); // OK,已窄化
  }
}

// any: 绕过检查(不推荐)
function risky(value: any) {
  value.toUpperCase();       // 编译通过,运行可能报错
  value.nonExistentMethod(); // 编译通过
}

61. null 与 undefined

typescript 复制代码
// strictNullChecks: true 时
let a: null = null;             // OK
let b: undefined = undefined;   // OK
let c: string = null;           // Error
let d: string | null = null;    // OK

// 可选链与空值合并
let user = { profile: { name: "Alice" } };
let name = user?.profile?.name;  // 可选链
let count = user?.count ?? 0;    // 空值合并

62. object 类型

typescript 复制代码
// object: 表示非原始类型
let obj: object = { a: 1 };
let fn: object = () => {};
// let num: object = 42; // Error: 原始类型

// {} 空对象类型:允许任何非 null/undefined 的值
let x: {};
x = 1;
x = "str";
x = {};
// x = null; // Error

63. TypeScript 元组

定义: 元组是已知固定长度和各元素类型的数组。

typescript 复制代码
// 基本元组
let tuple: [string, number] = ["hello", 42];

// 命名元组元素(TS 4.0+)
type Response = [code: number, message: string];
let res: Response = [200, "OK"];

// 可选元素
let optionalTuple: [string, number?];
optionalTuple = ["hello"];

// 剩余元素
let restTuple: [string, ...number[]];
restTuple = ["hello", 1, 2, 3];

// 只读元组
let readonlyTuple: readonly [string, number] = ["hi", 1];
// readonlyTuple[0] = "bye"; // Error

64. 函数重载

定义: 函数重载允许同一个函数有多个签名定义,根据参数不同调用不同实现。

typescript 复制代码
// 重载签名
function add(a: string, b: string): string;
function add(a: number, b: number): number;

// 实现签名
function add(a: string | number, b: string | number) {
  if (typeof a === "string" && typeof b === "string") {
    return a + b;
  }
  return (a as number) + (b as number);
}

add("a", "b"); // string
add(1, 2);     // number
// add("a", 1); // Error: 没有匹配的重载

65. 可调用类型与可构造类型

typescript 复制代码
// 可调用类型
interface AddFn {
  (a: number, b: number): number;
}
const add: AddFn = (a, b) => a + b;

// 箭头函数类型
type MultiplyFn = (a: number, b: number) => number;

// 可构造类型
interface ClockConstructor {
  new (hour: number, minute: number): ClockInterface;
}
interface ClockInterface {
  tick(): void;
}

66. 映射类型的定义方式

定义: 映射类型基于已有类型创建新类型,通过遍历属性键来变换类型。

typescript 复制代码
// 基本映射
type MyReadonly<T> = { readonly [P in keyof T]: T[P] };
type MyOptional<T> = { [P in keyof T]?: T[P] };

// 带 as 子句的映射(TS 4.1+)
type Getters<T> = {
  [P in keyof T as `get${Capitalize<string & P>}`]: () => T[P];
};

interface Person { name: string; age: number; }
type LazyPerson = Getters<Person>;
// { getName(): string; getAge(): number }

// 过滤属性
type RemoveKindField<T> = {
  [P in keyof T as Exclude<P, "kind">]: T[P];
};

67. 条件类型的语法和应用

定义: 条件类型根据类型关系选择不同类型的表达式,语法类似三元运算符。

typescript 复制代码
// 基本语法: T extends U ? X : Y
type IsString<T> = T extends string ? true : false;
type A = IsString<string>;  // true
type B = IsString<number>;  // false

// 分布式条件类型(联合类型分发)
type ToArray<T> = T extends any ? T[] : never;
type A1 = ToArray<string | number>; // string[] | number[]

// 嵌套条件类型
type Flatten<T> = T extends Array<infer U> ? U : T;

68. infer 关键字的使用

定义: infer 用于在条件类型中推断类型变量,通常用于提取函数参数、返回值等。

typescript 复制代码
// 提取返回值类型
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any;

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

// 提取 Promise 值类型
type UnpackPromise<T> = T extends Promise<infer U> ? U : T;
type A = UnpackPromise<Promise<string>>; // string

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

// 提取构造函数类型
type ConstructorParameters<T> = T extends new (...args: infer P) => any ? P : never;

69. 递归类型别名

typescript 复制代码
// 递归深度只读
type DeepReadonly<T> = {
  readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P];
};

// 递归去除 Promise
type Awaited<T> = T extends Promise<infer U> ? Awaited<U> : T;

// 递归路径类型
type Path<T> = T extends object
  ? {
      [K in keyof T]: K extends string
        ? K | `${K}.${Path<T[K]>}`
        : never;
    }[keyof T]
  : never;

70. 模板字面量类型

typescript 复制代码
// 基本使用
type World = "world";
type Greeting = `hello ${World}`; // "hello world"

// 联合类型展开
type EventName = "click" | "hover" | "focus";
type EventMethod = `on${Capitalize<EventName>}Change`;
// "onClickChange" | "onHoverChange" | "onFocusChange"

// 类型推断
type GetProp<T, K extends string> = K extends `${infer First}.${infer Rest}`
  ? First extends keyof T
    ? GetProp<T[First], Rest>
    : never
  : K extends keyof T
    ? T[K]
    : never;

71. 索引类型(索引访问类型)

typescript 复制代码
interface Person {
  name: string;
  age: number;
  location: { city: string; state: string };
}

type NameType = Person["name"];           // string
type LocationType = Person["location"];   // { city: string; state: string }
type CityType = Person["location"]["city"]; // string

// 联合索引
type NameOrAge = Person["name" | "age"]; // string | number

// keyof 操作符
type PersonKeys = keyof Person; // "name" | "age" | "location"

72. 类型体操 / 类型挑战

定义: 类型体操是利用 TypeScript 高级类型特性进行复杂类型变换的编程技巧。

typescript 复制代码
// DeepPartial
type DeepPartial<T> = {
  [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
};

// TupleToUnion
type TupleToUnion<T extends any[]> = T[number];
type A = TupleToUnion<[1, 2, 3]>; // 1 | 2 | 3

// LastElement
type Last<T extends any[]> = T extends [...any[], infer L] ? L : never;
type B = Last<[1, 2, 3]>; // 3

// Promise.all 返回值类型
type PromiseAll<T extends any[]> = Promise<{
  [K in keyof T]: T[K] extends Promise<infer R> ? R : T[K];
}>;

十一、TypeScript 与 JavaScript 的对比

73. TypeScript 与 JavaScript 的区别

维度 JavaScript TypeScript
类型系统 动态类型 静态类型(编译时)
文件扩展名 .js .ts / .tsx
运行方式 直接运行 需编译为 JS
类型检查 运行时 编译时
可选参数 需要判断 原生支持
接口支持
泛型支持
枚举支持
装饰器 无(提案中) 实验性支持
学习曲线 中等
开发效率 小项目高 大项目高

74. TypeScript 的优缺点有哪些?

优点:

  1. 静态类型检查: 编译时发现类型错误,减少运行时 bug
  2. 更好的 IDE 支持: 智能补全、跳转定义、重构
  3. 自文档化: 类型即文档,提高代码可读性
  4. 大规模项目友好: 类型约束使重构更安全
  5. 渐进式采用: 可以逐步从 JS 迁移
  6. 丰富的工具类型: 内置多种类型变换工具
  7. 社区生态: 主流框架和库都有 TS 支持
  8. 向后兼容: 兼容所有 JavaScript 特性

缺点:

  1. 学习曲线: 需要学习类型系统、泛型等概念
  2. 编译时间: 大型项目编译较慢
  3. 配置复杂度: tsconfig.json 配置项繁多
  4. 第三方库兼容: 部分库缺少类型声明
  5. 假安全感: 类型正确不等于逻辑正确
  6. 类型声明维护: 需要额外维护 .d.ts 文件

十二、TypeScript 生态与集成

75. 如何集成 TypeScript?

步骤:

bash 复制代码
# 1. 安装 TypeScript
npm install -D typescript

# 2. 初始化配置
npx tsc --init

# 3. 安装类型声明
npm install -D @types/node
npm install -D @types/express

# 4. 编译
npx tsc

与构建工具集成:

javascript 复制代码
// Webpack + ts-loader
{
  module: {
    rules: [
      { test: /\.tsx?$/, use: 'ts-loader', exclude: /node_modules/ }
    ]
  },
  resolve: { extensions: ['.tsx', '.ts', '.js'] }
}

// Vite(内置 TS 支持)
import { defineConfig } from 'vite';
export default defineConfig({ plugins: [] });

// Babel
// babel.config.js
module.exports = {
  presets: [
    ["@babel/preset-typescript", { isTSX: true, allExtensions: true }]
  ]
};

76. TypeScript 与 React

typescript 复制代码
// 函数组件
interface Props {
  name: string;
  age?: number;
  onClick: (id: string) => void;
}

const UserCard: React.FC<Props> = ({ name, age = 0, onClick }) => {
  return <div onClick={() => onClick("user-id")}>{name}</div>;
};

// Hooks 类型
const [count, setCount] = useState<number>(0);
const [user, setUser] = useState<User | null>(null);
const inputRef = useRef<HTMLInputElement>(null);

// 泛型组件
interface ListProps<T> {
  items: T[];
  renderItem: (item: T) => React.ReactNode;
}
function List<T>({ items, renderItem }: ListProps<T>) {
  return <ul>{items.map(renderItem)}</ul>;
}

// 事件类型
const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
  console.log(e.currentTarget);
};

77. TypeScript 与 Vue

typescript 复制代码
// Vue 3 Composition API
import { ref, computed, defineComponent } from "vue";

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

const user = ref<User | null>(null);
const count = ref(0);
const doubleCount = computed<number>(() => count.value * 2);

// defineComponent
export default defineComponent({
  props: {
    name: { type: String, required: true },
    age: { type: Number }
  },
  setup(props) {
    return { count, doubleCount };
  }
});

// 自定义 Hook
function useCounter(initial = 0) {
  const count = ref(initial);
  const increment = () => count.value++;
  return { count, increment };
}

78. TypeScript 与 Node.js

typescript 复制代码
import express, { Request, Response, NextFunction } from "express";

interface UserRequest extends Request { userId?: string; }

const app = express();

app.get("/users/:id", (req: Request, res: Response) => {
  const id = parseInt(req.params.id);
  res.json({ id, name: "Alice" });
});

function auth(req: UserRequest, res: Response, next: NextFunction) {
  req.userId = "123";
  next();
}

tsconfig for Node:

json 复制代码
{
  "compilerOptions": {
    "target": "ES2020",
    "module": "CommonJS",
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true
  }
}

79. TypeScript 与 ESLint / Jest

javascript 复制代码
// .eslintrc.js
module.exports = {
  parser: "@typescript-eslint/parser",
  plugins: ["@typescript-eslint"],
  extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
  rules: {
    "@typescript-eslint/no-explicit-any": "warn",
    "@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }]
  }
};

// jest.config.js
module.exports = {
  preset: "ts-jest",
  testEnvironment: "node",
  moduleFileExtensions: ["ts", "tsx", "js", "json"]
};

80. TypeScript 与 Webpack / Vite

javascript 复制代码
// webpack.config.js
module.exports = {
  entry: "./src/index.ts",
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        use: [{ loader: "ts-loader", options: { transpileOnly: true } }],
        exclude: /node_modules/
      }
    ]
  },
  resolve: { extensions: [".ts", ".tsx", ".js"] },
  output: { filename: "bundle.js", path: path.resolve(__dirname, "dist") }
};

// vite.config.ts
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
export default defineConfig({ plugins: [react()] });

十三、类型声明

81. TypeScript 类型声明

定义: 类型声明用于描述代码中的类型信息,包括 .d.ts 声明文件和内联类型注解。

typescript 复制代码
// 内联类型声明
function greet(name: string): string {
  return `Hello, ${name}`;
}

// 声明文件
declare module "my-lib" {
  export function doSomething(): string;
}

82. 第三方库类型声明

bash 复制代码
# 安装类型包
npm install -D @types/lodash
npm install -D @types/react

手动声明:

typescript 复制代码
// types/my-lib.d.ts
declare module "my-lib" {
  export function doSomething(): string;
  export const VERSION: string;
}

83. 全局类型声明

typescript 复制代码
// types/global.d.ts
declare global {
  interface Window {
    myCustomProperty: string;
  }
}
export {};

84. 类型声明最佳实践

  1. 优先使用 DefinitelyTyped@types/*
  2. 模块声明放 types 目录declare module
  3. 全局声明确保 export {}:避免污染全局
  4. 使用 import type:仅类型导入,编译后移除
  5. 避免声明文件中的 any :使用 unknown 替代

十四、项目实战

85. TypeScript 项目结构

bash 复制代码
project/
├── src/
│   ├── types/           # 全局类型声明
│   │   └── index.d.ts
│   ├── interfaces/      # 接口定义
│   ├── models/          # 数据模型
│   ├── services/        # 业务逻辑
│   ├── components/      # UI 组件
│   ├── utils/           # 工具函数
│   └── index.ts         # 入口文件
├── tsconfig.json        # TS 配置
├── package.json
└── .eslintrc.js

86. TypeScript 最佳实践

typescript 复制代码
// 1. 启用严格模式
// tsconfig: { "strict": true }

// 2. 避免使用 any,改用 unknown
function process(data: unknown) {
  if (typeof data === "object" && data !== null) { /* ... */ }
}

// 3. 使用 as const 不变断言
const routes = [
  { path: "/", name: "home" },
  { path: "/about", name: "about" }
] as const;

// 4. 使用 satisfies 运算符(TS 4.9+)
const config = { host: "localhost", port: 3000 } satisfies Record<string, string | number>;

// 5. 优先使用 interface 定义对象
interface User { id: string; name: string; }

// 6. 使用类型守卫进行类型窄化
function isUser(data: unknown): data is User {
  return typeof data === "object" && data !== null && "id" in data;
}

// 7. 合理使用泛型约束
function merge<T extends object, U extends object>(a: T, b: U): T & U {
  return { ...a, ...b };
}

// 8. 使用类型导入优化编译
import type { User } from "./types";

87. JavaScript 项目迁移到 TypeScript

渐进式迁移步骤:

  1. 添加 TypeScript 依赖

    bash 复制代码
    npm install -D typescript @types/node
  2. 创建宽松配置

    json 复制代码
    {
      "compilerOptions": {
        "allowJs": true,
        "checkJs": false,
        "strict": false,
        "outDir": "./dist"
      }
    }
  3. 逐步重命名 .js 为 .ts

    • 从工具函数和纯逻辑文件开始
    • 添加类型注解
    • 逐步启用 strict 模式
  4. 安装第三方库类型声明

    bash 复制代码
    npm install -D @types/lodash @types/express
  5. 最终目标配置

    json 复制代码
    { "compilerOptions": { "strict": true, "noImplicitAny": true, "allowJs": false } }

88. TypeScript 编译原理

编译阶段:

  1. 词法分析: 将源代码分解为 Token 流
  2. 语法分析: 构建抽象语法树(AST)
  3. 语义分析: 类型检查和绑定
  4. 代码生成: 输出目标 JavaScript

AST 结构示例:

scss 复制代码
SourceFile
  └── FunctionDeclaration
        ├── Identifier (name)
        ├── Parameter (type)
        └── Block
              └── ReturnStatement

89. TypeScript 调试与源码映射

json 复制代码
{
  "compilerOptions": {
    "sourceMap": true
  }
}

VS Code 调试配置:

json 复制代码
{
  "version": "0.2.0",
  "configurations": [
    {
      "type": "node",
      "request": "launch",
      "name": "Debug TS",
      "program": "${workspaceFolder}/src/index.ts",
      "preLaunchTask": "tsc: build",
      "sourceMaps": true
    }
  ]
}

90. TypeScript 常见错误

错误码 原因 解决方案
TS2322 类型不匹配 检查赋值类型
TS2345 参数类型不兼容 检查函数签名
TS2339 属性不存在 检查类型定义或使用索引访问
TS2304 找不到名称 导入或声明类型
TS7006 参数隐式 any 添加类型注解
TS2532 对象可能是 undefined 使用可选链或类型守卫

91. TypeScript 性能优化

  1. 使用 skipLibCheck: true 跳过声明文件检查
  2. 使用 incremental: true 启用增量编译
  3. 使用 ts-loadertranspileOnly 模式
  4. 合理配置 include/exclude
  5. 使用 // @ts-nocheck 跳过特定文件
  6. 使用项目引用(Project References) 拆分大项目
  7. 避免过深的类型嵌套
  8. 减少 any 使用 提高类型推断效率
json 复制代码
{
  "compilerOptions": {
    "skipLibCheck": true,
    "incremental": true,
    "tsBuildInfoFile": "./node_modules/.cache/tsbuildinfo.json"
  }
}
相关推荐
walking9571 小时前
重新学习前端之Linux
前端·vue.js·面试
walking9571 小时前
重新学习前端之CSS
前端·vue.js·面试
walking9571 小时前
重新学习前端之Git
前端·vue.js·面试
walking9571 小时前
重新学习前端之小程序
前端
魔术师Grace1 小时前
AI让我退化成原始人了
前端·程序员
铁皮饭盒1 小时前
今天你会学到这些关键词
前端·后端
李剑一1 小时前
耗时 2 小时!我复刻了全网超火的通透 3D 水晶球动效,Vue3+Three.js 做出高级视觉特效
前端·three.js
oil欧哟2 小时前
🤔 很长时间没写文章了,分享一下最近的一些思考
前端·后端
Hello--_--World2 小时前
Vue指令:v-if vs v-show、v-if 与 v-for 的优先级冲突、自定义指令
前端·javascript·vue.js