TypeScript 进阶:泛型、条件类型、类型守卫与装饰器

系列 :Java 开发者的 Node.js + TypeScript 之路(第 6 篇) 适用人群:有 Java 基础,想转向 Node.js 后端开发的开发者


上一篇我们介绍了 TypeScript 类型系统的基础。本文深入泛型、条件类型、映射类型、类型守卫和装饰器等高级特性------特别是装饰器,它是 NestJS 框架的核心机制。


1. 泛型(Generics)

基础泛型

typescript 复制代码
// 类似 Java 的泛型,但更灵活
function identity<T>(value: T): T {
  return value;
}

const num = identity<number>(42);     // 显式指定
const str = identity('hello');        // 类型推断(推荐)

// 泛型类
class Result<T> {
  constructor(public data: T, public error?: string) {}
}

const ok = new Result({ id: 1 }); // T 推断为 { id: number }

Java 对比 :TS 泛型类似 Java 泛型 <T>,但 TS 泛型在编译后被擦除(类似 Java 的类型擦除),不会生成额外代码。

泛型约束

typescript 复制代码
// 约束 T 必须有 length 属性
function logLength<T extends { length: number }>(value: T): void {
  console.log(value.length);
}

logLength('hello');    // string 有 length
logLength([1, 2, 3]);  // array 有 length
// logLength(123);     // number 没有 length

// 约束 T 必须是某个接口的子类型
interface HasId {
  id: number;
}

function findById<T extends HasId>(items: T[], id: number): T | undefined {
  return items.find(item => item.id === id);
}

Java 对比<T extends HasId> 相当于 Java 的 <T extends HasId>,语法完全一致。

多泛型参数

typescript 复制代码
function merge<A, B>(a: A, b: B): A & B {
  return { ...a, ...b };
}

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

泛型工具类型(内置)

typescript 复制代码
// TS 内置的泛型工具类型,日常开发频繁使用
Partial<T>       // 所有属性变可选
Required<T>      // 所有属性变必选
Readonly<T>      // 所有属性变只读
Pick<T, K>       // 选取 K 个属性
Omit<T, K>       // 排除 K 个属性
Record<K, V>     // 键为 K,值为 V 的对象类型
Exclude<T, U>    // 从 T 中排除 U
Extract<T, U>    // 从 T 中提取 U
ReturnType<T>    // 获取函数返回值类型
Parameters<T>    // 获取函数参数类型(元组)

Java 对比 :Java 没有内置的类型映射工具,TS 的 PartialPickOmit 等是独有的优势,能大幅减少 DTO 类定义。

自定义工具类型

typescript 复制代码
// 让某些属性变为可选
type PartialBy<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;

type User = { id: number; name: string; email: string };
type UserWithOptionalEmail = PartialBy<User, 'email'>;
// { id: number; name: string; email?: string }

// 让某些属性变为只读
type ReadonlyBy<T, K extends keyof T> = Omit<T, K> & Readonly<Pick<T, K>>;

2. 条件类型

typescript 复制代码
// 语法:T extends U ? X : Y(类似三元表达式)

type IsString<T> = T extends string ? 'yes' : 'no';

type A = IsString<string>; // 'yes'
type B = IsString<number>; // 'no'

// 实用场景:提取 Promise 的返回值类型
type UnwrapPromise<T> = T extends Promise<infer R> ? R : T;

type C = UnwrapPromise<Promise<string>>; // string
type D = UnwrapPromise<number>;          // number

分布式条件类型

typescript 复制代码
// 当 T 是联合类型时,条件类型会"分布"到每个成员

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

type Result = ToArray<string | number>;
// string[] | number[](而不是 (string | number)[])

infer 关键字

typescript 复制代码
// 在条件类型中推断类型

// 提取函数返回值类型
type MyReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
type R = MyReturnType<() => string>; // string

// 提取函数参数类型
type MyParameters<T> = T extends (...args: infer P) => any ? P : never;
type P = MyParameters<(a: string, b: number) => void>; // [string, number]

// 提取数组元素类型
type ElementOf<T> = T extends (infer E)[] ? E : never;
type E = ElementOf<string[]>; // string

3. 映射类型

typescript 复制代码
// 遍历联合类型或 keyof,生成新类型

// 将所有属性变为可选(Partial 的实现原理)
type MyPartial<T> = {
  [K in keyof T]?: T[K];
};

// 将所有属性变为 null 允许
type Nullable<T> = {
  [K in keyof T]: T[K] | null;
};

type NullableUser = Nullable<User>;
// { id: number | null; name: string | null; email: string | null }

// 重新映射键名(as 关键字)
type Getters<T> = {
  [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};

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

4. 类型守卫

typescript 复制代码
// 类型守卫是在运行时检查类型的表达式

// typeof 守卫
function isString(value: unknown): value is string {
  return typeof value === 'string';
}

// in 守卫
interface Fish { swim(): void }
interface Bird { fly(): void }

function isFish(pet: Fish | Bird): pet is Fish {
  return 'swim' in pet;
}

// instanceof 守卫(对 class 实例有效)
function handleError(err: Error | string) {
  if (err instanceof Error) {
    console.log(err.message); // 类型收窄为 Error
  } else {
    console.log(err);         // string
  }
}

// 可辨识联合(最推荐的守卫方式)
type Shape =
  | { kind: 'circle'; radius: number }
  | { kind: 'rect'; width: number; height: number };

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

never 的穷尽检查

typescript 复制代码
function assertNever(x: never): never {
  throw new Error(`Unexpected value: ${x}`);
}

function area(shape: Shape): number {
  switch (shape.kind) {
    case 'circle':
      return Math.PI * shape.radius ** 2;
    case 'rect':
      return shape.width * shape.height;
    default:
      return assertNever(shape); // 如果漏掉某个 case,编译报错
  }
}

5. 装饰器(Decorators)

重要 :装饰器是 NestJS 的核心机制,必须掌握。 需要开启 tsconfig.json 中的 "experimentalDecorators": true
Java 对比 :装饰器非常类似 Java 的注解(Annotation),如 @Override@RestController。NestJS 中的 @Controller()@Get() 就是装饰器。

类装饰器

typescript 复制代码
// 类装饰器接收构造函数作为参数
function Logger(target: Function) {
  console.log(`Class created: ${target.name}`);
}

@Logger
class UserService {
  // ...
}
// 输出:Class created: UserService

方法装饰器

typescript 复制代码
// 接收三个参数:原型、方法名、属性描述符
function Log(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  const original = descriptor.value;

  descriptor.value = function (...args: any[]) {
    console.log(`Calling ${propertyKey} with args:`, args);
    const result = original.apply(this, args);
    console.log(`${propertyKey} returned:`, result);
    return result;
  };
}

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

// 调用时会自动打印日志
new Calculator().add(1, 2);
// Calling add with args: [1, 2]
// add returned: 3

属性装饰器

typescript 复制代码
function Required(target: any, propertyKey: string) {
  const requiredProps: string[] = target.__requiredProps || [];
  requiredProps.push(propertyKey);
  target.__requiredProps = requiredProps;
}

class CreateUserDto {
  @Required
  name!: string;

  @Required
  email!: string;
}

参数装饰器

typescript 复制代码
// NestJS 大量使用参数装饰器
// 例如 @Body()、@Param()、@Query()

function CurrentUser(target: any, propertyKey: string, parameterIndex: number) {
  const existing = Reflect.getMetadata('currentUser', target, propertyKey) || [];
  existing.push(parameterIndex);
  Reflect.defineMetadata('currentUser', existing, target, propertyKey);
}

class UserController {
  getProfile(@CurrentUser() user: User) {
    return user;
  }
}

装饰器工厂(带参数的装饰器)

typescript 复制代码
// 返回装饰器函数的函数
function Validate(minLength: number) {
  return function (target: any, propertyKey: string) {
    let value: string;

    Object.defineProperty(target, propertyKey, {
      get() { return value; },
      set(newValue: string) {
        if (newValue.length < minLength) {
          throw new Error(`${propertyKey} must be at least ${minLength} chars`);
        }
        value = newValue;
      }
    });
  };
}

class User {
  @Validate(3)
  name!: string;
}

6. 模块与命名空间

typescript 复制代码
// TypeScript 完全兼容 ES Modules
// 推荐使用 import/export,不使用 namespace

// types.ts
export interface User { id: number; name: string; }
export type UserId = number;

// service.ts
import { User, UserId } from './types';
import type { Config } from './config'; // type-only import(不会出现在编译结果中)

// index.ts
export * from './types';     // 重导出
export { default as App } from './app';

7. 实用配置(tsconfig.json)

json 复制代码
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "outDir": "./dist",
    "rootDir": "./src",
    "declaration": true,
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "resolveJsonModule": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}

strict: true 包含了所有严格检查,新项目中必须开启


小结

概念 说明 Java 对标
泛型 <T> 参数化类型 泛型 <T>
泛型约束 T extends X <T extends X>
条件类型 T extends U ? X : Y 无直接对应
infer 条件类型中推断类型 无直接对应
映射类型 [K in keyof T] 无直接对应
类型守卫 typeof / instanceof / in instanceof + 转型
可辨识联合 kind 字段区分类型 sealed class + pattern matching
装饰器 @Decorator 元编程 注解 @Annotation
装饰器工厂 带参数的装饰器 带参数的注解

下一篇预告:Node.js 基础与模块系统:CommonJS vs ESM 完全指南

相关推荐
swipe14 小时前
从 0 到 1 实现大文件上传:分片、秒传、断点续传、暂停、重试与服务端合并
前端·javascript·面试
kyriewen16 小时前
AI 生成的代码能跑就行?这 5 个坑迟早炸
前端·javascript·ai编程
kisshyshy16 小时前
🍦 雪糕、食堂、火车厢:三幅漫画吃透栈、队列与链表
javascript·算法
胡志辉16 小时前
从v8源码和react深入浅出理解 JavaScript 作用域链与闭包
前端·javascript
Bolt17 小时前
TypeScript 7.0 来了:当 tsc 用 Go 重写之后
javascript·typescript·go
阳火锅19 小时前
😭测试小姐姐终于不骂我了!这个提BUG神器太香了...
前端·javascript·面试
林希_Rachel_傻希希21 小时前
js里面的proxy理解。以及vue3响应式数据设计底层
前端·javascript·面试
阿黎梨梨21 小时前
AI Loop:告别“人肉写提示词”,让代码替你“鞭策”AI
javascript·人工智能
竹林8181 天前
用 wagmi v2 + viem 监听链上事件,我踩了三天坑终于搞懂了实时日志与历史补全
javascript