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参数
关键实现说明
- 类型映射:通过
ParamTypeMap将字符串类型标识转换为TypeScript原生类型,解决伪代码中用字符串表示类型的问题。 - 泛型约束:
SignalTarget的泛型Signals定义了子类的所有信号结构,connect和emit方法通过SignalName extends keyof Signals约束只能传入合法的信号名称。 - 参数类型推导:利用
MapParams和MapReturn辅助类型,自动将信号定义中的类型标识转换为实际的函数参数和返回值类型,实现严格的类型校验。
内容的提问来源于stack exchange,提问作者vixalien
相关产品推荐
相关产品推荐

