You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

TypeScript如何基于静态属性实现类型约束与回调类型校验?

问题:TypeScript实现带严格类型校验的信号订阅/发布抽象类

期望实现一个抽象类SignalTarget,子类继承后:

  • connect方法仅允许传入子类定义的信号名称作为第一个参数
  • 回调函数的参数和返回值具备完整的类型校验

以下是用户提供的伪代码示例:

type Params = "string" | "bool" | "number" | "bigint";
type Return = Params | "void";

abstract class SignalTarget<Signals extends Record<string, { parameters: readonly Params[]; result: "void" }>> {
  static signals: Record<string, { parameters: readonly Params[]; result: "void" }>;

  _signals: Partial<Record<string, Function[]>> = {}

  connect (name: string, callback: Function) {
    const signals = this._signals[name];

    this._signals[name] ||= [
      ...(signals ?? []),
      callback
    ]
  }

  emit(name: string, ...args: any[]) {
    const signals = this._signals[name];

    (signals ?? []).map(signal => signal.call(this, ...args));
  }

  /*
  connect<Signal extends keyof Signals, Declarations extends Signals[Signal]>(name: Signal, callback: (...args: Declarations["parameters"]) => Declarations["result"]) {

  } */
}

// 期望的子类扩展方式
class Log extends SignalTarget<{ log: { parameters: ["string"], result: "void" } }> {
  static signals = {
   log: {
     parameters: ["string"],
     result: "void"
   }
  } as const

  log (message: string) {
    console.log("message");
    this.emit("log", message);
  }
}

const logger = new Log;
logger.connect("log", (message) => console.log("The log method was called!: ", message));
解决方案:可以实现,以下是完整的TypeScript代码

核心是通过类型映射将字符串类型标识(如"string")转换为TypeScript实际类型,并利用泛型约束connect和emit方法的参数:

// 定义类型标识到实际TypeScript类型的映射
type ParamTypeMap = {
  "string": string;
  "bool": boolean;
  "number": number;
  "bigint": bigint;
};

type Params = keyof ParamTypeMap;
type Return = Params | "void";

// 辅助类型:将参数类型标识数组转换为实际类型数组
type MapParams<T extends readonly Params[]> = {
  [K in keyof T]: ParamTypeMap[T[K]];
};

// 辅助类型:将返回值类型标识转换为实际类型
type MapReturn<T extends Return> = T extends "void" ? void : ParamTypeMap[T];

abstract class SignalTarget<Signals extends Record<string, { parameters: readonly Params[]; result: Return }>> {
  static signals: Record<string, { parameters: readonly Params[]; result: Return }>;

  _signals: Partial<Record<keyof Signals, Function[]>> = {};

  // 带类型校验的connect方法
  connect<SignalName extends keyof Signals>(
    name: SignalName,
    callback: (...args: MapParams<Signals[SignalName]["parameters"]>) => MapReturn<Signals[SignalName]["result"]>
  ): void {
    const existingCallbacks = this._signals[name] ?? [];
    this._signals[name] = [...existingCallbacks, callback];
  }

  // 带类型校验的emit方法
  emit<SignalName extends keyof Signals>(
    name: SignalName,
    ...args: MapParams<Signals[SignalName]["parameters"]>
  ): void {
    const callbacks = this._signals[name] ?? [];
    callbacks.forEach(callback => callback.call(this, ...args));
  }
}

// 子类示例:Log类
class Log extends SignalTarget<{
  log: { parameters: ["string"], result: "void" };
  // 可以添加更多信号,比如error
  error: { parameters: ["string", "bool"], result: "void" };
}> {
  static signals = {
    log: { parameters: ["string"], result: "void" },
    error: { parameters: ["string", "bool"], result: "void" }
  } as const;

  log(message: string) {
    console.log("原始日志:", message);
    this.emit("log", message);
  }

  error(message: string, isCritical: boolean) {
    console.error("错误日志:", message, "是否严重:", isCritical);
    this.emit("error", message, isCritical);
  }
}

// 使用示例
const logger = new Log();

// 正确:只能传入"log"或"error",回调参数类型自动推导
logger.connect("log", (message) => {
  console.log("订阅到log信号:", message);
  // message类型是string,类型校验通过
});

logger.connect("error", (message, isCritical) => {
  console.log("订阅到error信号:", message, isCritical);
  // message是string,isCritical是boolean,类型校验通过
});

// 错误示例:传入不存在的信号名称会报错
// logger.connect("warn", () => {}); // 类型错误:"warn"不是Log的信号名称

// 错误示例:回调参数数量/类型不匹配会报错
// logger.connect("log", (message, extra) => {}); // 类型错误:参数数量不匹配
// logger.connect("error", (message) => {}); // 类型错误:缺少isCritical参数

关键实现说明

  1. 类型映射:通过ParamTypeMap将字符串类型标识转换为TypeScript原生类型,解决伪代码中用字符串表示类型的问题。
  2. 泛型约束:SignalTarget的泛型Signals定义了子类的所有信号结构,connect和emit方法通过SignalName extends keyof Signals约束只能传入合法的信号名称。
  3. 参数类型推导:利用MapParams和MapReturn辅助类型,自动将信号定义中的类型标识转换为实际的函数参数和返回值类型,实现严格的类型校验。

内容的提问来源于stack exchange,提问作者vixalien

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.10 04:40:27