系列 :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 的
Partial、Pick、Omit等是独有的优势,能大幅减少 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 完全指南