跳到主要内容

TypeScript 高级特性

TypeScript 提供了许多高级类型和特性,掌握这些可以帮助我们写出更加健壮和类型安全的代码。

高级类型

1. 交叉类型(Intersection Types)

使用 & 运算符将多个类型合并为一个类型:

interface BusinessPartner {
name: string;
credit: number;
}

interface Identity {
id: number;
email: string;
}

type Employee = BusinessPartner & Identity;

// 使用示例
let emp: Employee = {
name: "John",
credit: 100,
id: 1234,
email: "john@example.com"
};

2. 联合类型(Union Types)

使用 | 运算符表示一个值可以是几种类型之一:

type StringOrNumber = string | number;
type Status = "success" | "error" | "pending";

function printId(id: number | string) {
if (typeof id === "string") {
console.log(id.toUpperCase());
} else {
console.log(id);
}
}

3. 类型守卫(Type Guards)

帮助TypeScript推断类型的表达式:

// typeof 类型守卫
function padLeft(value: string, padding: string | number) {
if (typeof padding === "number") {
return Array(padding + 1).join(" ") + value;
}
if (typeof padding === "string") {
return padding + value;
}
throw new Error("Expected string or number");
}

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

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

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

4. 映射类型(Mapped Types)

从现有类型创建新类型:

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

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

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

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

5. 条件类型(Conditional Types)

根据条件选择类型:

type NonNullable<T> = T extends null | undefined ? never : T;

// 示例
type EmailAddress = string | null | undefined;
type NonNullableEmail = NonNullable<EmailAddress>; // string

// 条件类型与泛型结合
type ArrayType<T> = T extends any[] ? T[number] : T;
type StringArray = ArrayType<string[]>; // string
type NumberValue = ArrayType<number>; // number

高级特性

1. 装饰器(Decorators)

用于修改类的行为:

function logged(constructor: Function) {
console.log(`Creating new instance of ${constructor.name}`);
}

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

// 方法装饰器
function measure(
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
const originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
const start = performance.now();
const result = originalMethod.apply(this, args);
const finish = performance.now();
console.log(`${propertyKey} took ${finish - start}ms`);
return result;
};
}

2. 高级类型推断

// ReturnType
function f() {
return { x: 10, y: 3 };
}
type P = ReturnType<typeof f>; // { x: number, y: number }

// Parameters
type T0 = Parameters<() => string>; // []
type T1 = Parameters<(s: string) => void>; // [string]

// InstanceType
class C {
x = 0;
y = 0;
}
type T2 = InstanceType<typeof C>; // C

3. 模块增强(Module Augmentation)

扩展现有模块的类型定义:

// 原始模块
declare module "myModule" {
export interface BaseOptions {
timeout: number;
}
}

// 增强模块
declare module "myModule" {
interface BaseOptions {
retries?: number;
}
}

最佳实践

1. 类型安全

// 使用严格的类型检查
{
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true
}
}

// 避免使用 any
function processData<T>(data: T): T {
// 处理数据
return data;
}

2. 类型断言最佳实践

// 使用 as const 断言
const config = {
endpoint: "api.example.com",
port: 443
} as const;

// 双重断言(谨慎使用)
let someValue: unknown = "this is a string";
let strLength: number = (someValue as string).length;

3. 泛型约束最佳实践

interface HasLength {
length: number;
}

// 泛型约束确保参数有length属性
function logLength<T extends HasLength>(arg: T): number {
return arg.length;
}

// 可以传入字符串或数组
logLength("Hello"); // OK
logLength([1, 2, 3]); // OK
logLength(123); // Error: number doesn't have length

高级工具类型

// Omit - 从类型中排除某些属性
type User = {
id: number;
name: string;
email: string;
password: string;
};

type PublicUser = Omit<User, "password">;

// Extract - 提取符合条件的类型
type T0 = Extract<"a" | "b" | "c", "a" | "f">; // "a"

// Exclude - 排除符合条件的类型
type T1 = Exclude<"a" | "b" | "c", "a" | "b">; // "c"

// Required - 使所有属性必需
type Props = {
a?: number;
b?: string;
};

type RequiredProps = Required<Props>;

1. 装饰器(Decorators)

装饰器是一种特殊类型的声明,它能够被附加到类声明、方法、访问符、属性或参数上。装饰器使用 @expression 形式,expression 求值后必须为一个函数。

1.1 类装饰器

// 类装饰器
function classDecorator<T extends { new (...args: any[]): {} }>(constructor: T) {
return class extends constructor {
newProperty = "new property";
hello = "override";
}
}

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

console.log(new Greeter("world"));

1.2 方法装饰器

// 方法装饰器
function methodDecorator(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
// 保存原始方法
const originalMethod = descriptor.value;

// 修改方法行为
descriptor.value = function(...args: any[]) {
console.log(`Calling ${propertyKey} with args:`, args);
const result = originalMethod.apply(this, args);
console.log(`Method ${propertyKey} returned:`, result);
return result;
};
}

class Example {
@methodDecorator
greet(name: string) {
return `Hello, ${name}!`;
}
}

1.3 属性装饰器

// 属性装饰器
function propertyDecorator(target: any, propertyKey: string) {
// 属性getter
let value = target[propertyKey];

// 属性getter/setter
Object.defineProperty(target, propertyKey, {
get: () => value,
set: (newValue: any) => {
console.log(`Setting ${propertyKey} to:`, newValue);
value = newValue;
},
});
}

class User {
@propertyDecorator
name: string = "default";
}

1.4 参数装饰器

// 参数装饰器
function parameterDecorator(target: any, propertyKey: string, parameterIndex: number) {
console.log(`Parameter decorator: ${propertyKey}, index: ${parameterIndex}`);
}

class Service {
login(@parameterDecorator username: string, @parameterDecorator password: string) {
// 方法实现
}
}

1.5 装饰器工厂

// 装饰器工厂
function log(prefix: string) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;

descriptor.value = function(...args: any[]) {
console.log(`${prefix} ${propertyKey}`);
return originalMethod.apply(this, args);
};
};
}

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

2. 模块和命名空间

2.1 模块

TypeScript 中的模块可以帮助我们更好地组织代码,实现代码复用和封装。

// math.ts
export function add(x: number, y: number): number {
return x + y;
}

export function subtract(x: number, y: number): number {
return x - y;
}

// 默认导出
export default class Calculator {
add(x: number, y: number): number {
return x + y;
}
}

// main.ts
import Calculator, { add, subtract } from './math';

const calc = new Calculator();
console.log(calc.add(1, 2)); // 3
console.log(add(3, 4)); // 7
console.log(subtract(10, 5)); // 5

2.2 命名空间

命名空间可以用来避免命名冲突,将相关的代码组织在一起。

// 定义命名空间
namespace Validation {
export interface StringValidator {
isValid(s: string): boolean;
}

export class LettersOnlyValidator implements StringValidator {
isValid(s: string): boolean {
return /^[A-Za-z]+$/.test(s);
}
}

export class ZipCodeValidator implements StringValidator {
isValid(s: string): boolean {
return /^\d{5}(-\d{4})?$/.test(s);
}
}
}

// 使用命名空间
let validators: { [s: string]: Validation.StringValidator } = {};
validators["Letters"] = new Validation.LettersOnlyValidator();
validators["ZIP"] = new Validation.ZipCodeValidator();

3. 高级类型技巧

3.1 类型保护

// 类型谓词
function isString(value: any): value is string {
return typeof value === "string";
}

// instanceof 类型保护
class Bird {
fly() {}
layEggs() {}
}

class Fish {
swim() {}
layEggs() {}
}

function getSmallPet(): Fish | Bird {
return Math.random() > 0.5 ? new Bird() : new Fish();
}

let pet = getSmallPet();
if (pet instanceof Bird) {
pet.fly();
}

3.2 可辨识联合类型

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

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

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

type Shape = Square | Rectangle | Circle;

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

3.3 映射类型进阶

// 条件类型中的映射
type FunctionPropertyNames<T> = {
[K in keyof T]: T[K] extends Function ? K : never
}[keyof T];

interface User {
name: string;
age: number;
updateProfile: () => void;
}

type FunctionProps = FunctionPropertyNames<User>; // "updateProfile"

// 映射类型修饰符
type Mutable<T> = {
-readonly [P in keyof T]: T[P]
};

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

4. 声明合并

TypeScript中独特的概念,它可以将多个同名声明合并为一个定义。

// 接口合并
interface Box {
height: number;
width: number;
}

interface Box {
scale: number;
}

let box: Box = {height: 5, width: 6, scale: 10};

// 命名空间和类合并
class Album {
label: Album.AlbumLabel;
}

namespace Album {
export interface AlbumLabel {
name: string;
}
}

5. 实用工具类型

// 内置工具类型的高级用法
type DeepReadonly<T> = {
readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P]
};

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

// 条件类型工具
type Unpacked<T> = T extends (infer U)[]
? U
: T extends (...args: any[]) => infer U
? U
: T extends Promise<infer U>
? U
: T;

type T0 = Unpacked<string>; // string
type T1 = Unpacked<string[]>; // string
type T2 = Unpacked<() => string>; // string
type T3 = Unpacked<Promise<string>>; // string

6. 最佳实践

6.1 类型安全

// 严格的空值检查
function processValue(value: string | null | undefined) {
if (value != null) { // 同时检查null和undefined
console.log(value.toUpperCase());
}
}

// 使用类型断言时的安全检查
function assertIsString(value: any): asserts value is string {
if (typeof value !== "string") {
throw new Error("Value must be a string");
}
}

6.2 性能优化

// 避免过度使用泛型
// 不好的做法
function identity<T>(value: T): T {
return value;
}

// 更好的做法
function identity(value: any): any {
return value;
}

// 合理使用类型推断
// 不必要的类型注解
const numbers: number[] = [1, 2, 3];

// 让TypeScript自动推断
const numbers = [1, 2, 3];

6.3 代码组织

// 使用barrel文件组织导出
// models/index.ts
export * from './user.model';
export * from './product.model';
export * from './order.model';

// 使用命名空间组织相关功能
namespace App {
export namespace Utils {
export function format(value: string): string {
return value.trim();
}
}

export namespace Validation {
export function validate(value: string): boolean {
return value.length > 0;
}
}
}

7. 其他常见高级用法

前面那些内容更偏类型系统本身,这一节补一些项目里更常见、也更容易直接用上的高级写法。

7.1 infer:从已有类型里提取类型

infer 一般配合条件类型使用。它的作用很直接:如果某个类型满足某种结构,就把结构里的某一部分提取出来。

type MyReturnType<T> = T extends (...args: any[]) => infer R ? R : never;

function getUser() {
return { id: 1, name: "Tom" };
}

type User = MyReturnType<typeof getUser>;
// { id: number; name: string }

type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;

type A = UnwrapPromise<Promise<string>>; // string
type B = UnwrapPromise<number>; // number

7.2 模板字面量类型

模板字面量类型适合约束固定格式的字符串,比如事件名、位置名、主题变量名。

type Direction = "top" | "bottom" | "left" | "right";
type Align = "start" | "center" | "end";

type Position = `${Direction}-${Align}`;

let p1: Position = "top-start";
let p2: Position = "left-center";
// let p3: Position = "middle-center"; // Error

type EventName<T extends string> = `on${Capitalize<T>}`;

type ClickEvent = EventName<"click">; // "onClick"
type ChangeEvent = EventName<"change">; // "onChange"

7.3 键重映射

映射类型除了能遍历属性,还能通过 as 重新生成 key。这个技巧很适合做 getter、setter、接口映射。

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

type Getter<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K]
};

type UserGetter = Getter<User>;

// 结果:
// {
// getName: () => string;
// getAge: () => number;
// }

7.4 satisfies

satisfies 很实用。它能检查对象是否满足目标类型,同时尽量保留原始对象更精确的推断结果。

type Route = {
path: string;
needAuth: boolean;
};

const routes = {
home: { path: "/", needAuth: false },
profile: { path: "/profile", needAuth: true }
} satisfies Record<string, Route>;

// routes 需要符合 Record<string, Route>
// 但 routes.home.path 仍然能保留更具体的字面量信息

7.5 断言函数(asserts)

普通类型守卫是“判断一下”,断言函数更进一步:如果函数能正常执行完,TypeScript 就认为类型已经被缩小了。

function assertIsString(value: unknown): asserts value is string {
if (typeof value !== "string") {
throw new Error("value must be string");
}
}

function printUpperCase(value: unknown) {
assertIsString(value);
console.log(value.toUpperCase());
}

7.6 this 参数和链式调用

TypeScript 可以显式声明函数里的 this 类型。这个写法在工具函数、对象方法和链式 API 里都挺常见。

type User = {
name: string;
say(this: User): void;
};

const user: User = {
name: "Tom",
say() {
console.log(this.name);
}
};
class QueryBuilder {
private sql = "";

select(fields: string): this {
this.sql += `SELECT ${fields} `;
return this;
}

from(table: string): this {
this.sql += `FROM ${table}`;
return this;
}
}

7.7 typeof + keyof 联动

这组组合拳在“配置驱动”的代码里特别常见:先从常量值推导类型,再把这个类型拿去约束别的地方。

const statusMap = {
success: 200,
notFound: 404,
serverError: 500
} as const;

type StatusKey = keyof typeof statusMap;
type StatusCode = typeof statusMap[StatusKey];

let key: StatusKey = "success";
let code: StatusCode = 404;

7.8 never 做穷尽检查

可辨识联合真正好用的地方,是能在 switch 里做穷尽检查。以后如果新增一种类型,漏写分支时编译器会提醒你。

type Shape =
| { kind: "circle"; radius: number }
| { kind: "square"; size: number }
| { kind: "rectangle"; width: number; height: number };

function assertNever(value: never): never {
throw new Error(`Unhandled value: ${value}`);
}

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

7.9 const 泛型

这是 TS 新一些的写法,适合保留更精确的字面量类型,尤其在处理元组、路由配置、命令定义时很好用。

function makeTuple<const T extends readonly unknown[]>(value: T): T {
return value;
}

const t1 = makeTuple([1, 2, 3]);
// readonly [1, 2, 3]

const t2 = makeTuple(["get", "/users"]);
// readonly ["get", "/users"]

7.10 组合例子:从配置推导类型

这些能力放在一起,最典型的场景就是“配置写一次,类型自动跟着走”。

const api = {
getUser: { method: "GET", path: "/user/:id" },
createUser: { method: "POST", path: "/user" }
} as const;

type ApiName = keyof typeof api;
type ApiMethod = typeof api[ApiName]["method"];

function request(name: ApiName) {
return api[name];
}

const result = request("getUser");
// result.method 只能是 "GET"
// result.path 只能是 "/user/:id"

8. 使用建议

高级类型很强,但不是越复杂越好。

  • 普通接口能说明白的事,就别堆太多条件类型
  • 团队里没人看得懂的类型技巧,后期维护会很痛苦
  • infer、模板字面量类型、satisfies 这几个通常投入产出比很高
  • 复杂工具类型最好单独放到 types.ts 里,不要散落在业务文件中

一句话:让类型帮你兜底,不要让类型本身变成负担。