TypeScript中泛型参数能否用于自身extends约束?钩子互调方案问询
这确实是可行的!你遇到的问题主要是两个点:一是钩子函数的this绑定对象不对,二是泛型自引用约束的类型推断需要更精准的调整。让我们一步步解决:
第一步:修复钩子间的互相调用问题
原代码里你把钩子的this绑定到了单纯的config.context,但这个对象里只有上下文数据(比如foo),没有其他钩子方法(比如log、test),所以自然无法通过this访问其他钩子。我们需要把this绑定到最终返回的result对象——它既包含了上下文的data属性,又挂载了所有钩子方法。
同时,调整泛型约束,让钩子函数的this类型指向合并后的上下文(上下文数据 + 所有钩子):
type Config<TContext> = { context: TContext; }; type Hooks<THooks> = { hooks: THooks; }; type FunctionWithThis<T> = (this: T, ...args: any[]) => any; // 移除函数的this参数,得到可直接调用的函数类型 type RemoveThis<T extends Record<string, FunctionWithThis<any>>> = { [P in keyof T]: T[P] extends (this: infer _, ...a: infer A) => infer R ? (...a: A) => R : never }; // 合并后的上下文类型:包含原始上下文的data属性 + 所有钩子 type HookCombinedContext<TContext, THooks> = { data: TContext } & RemoveThis<THooks>; const configure = < TContext extends object, // 钩子的this类型指向合并后的上下文 THooks extends Record<string, FunctionWithThis<HookCombinedContext<TContext, THooks>>> >(config: Config<TContext> & Hooks<THooks>) => { const result: Partial<HookCombinedContext<TContext, THooks>> = { get data() { return config.context; } }; // 将钩子挂载到result,并绑定this为result Object.entries(config.hooks).forEach(([key, hook]) => { (result as any)[key] = (...args: any[]) => hook.call(result, ...args); }); return result as HookCombinedContext<TContext, THooks>; }; // 测试:现在test钩子可以调用this.log了! const engine = configure({ context: { foo: 12 }, hooks: { log() { console.log(this.data.foo); }, // 通过data访问上下文 test(str: string) { this.log(); // 成功调用log钩子 console.log(str); } } }); engine.test("hello"); // 输出12和hello
关键变化说明:
- 定义
HookCombinedContext明确钩子的this包含的成员:data(原始上下文)和所有钩子方法 - 钩子函数的
this绑定到result,而不是单纯的config.context - 泛型约束中让
THooks自引用HookCombinedContext,TypeScript能正确推断this的类型
第二步:扩展支持TCallbacks(钩子与回调互相调用)
接下来扩展到你提到的TCallbacks需求,逻辑和钩子类似——只需新增泛型参数,并扩展合并上下文的类型即可:
type Config<TContext> = { context: TContext; }; type Hooks<THooks> = { hooks: THooks; }; type Callbacks<TCallbacks> = { callbacks: TCallbacks; }; type FunctionWithThis<T> = (this: T, ...args: any[]) => any; type RemoveThis<T extends Record<string, FunctionWithThis<any>>> = { [P in keyof T]: T[P] extends (this: infer _, ...a: infer A) => infer R ? (...a: A) => R : never }; // 合并后的完整上下文:data(原始上下文) + 钩子 + 回调 type FullCombinedContext<TContext, THooks, TCallbacks> = { data: TContext } & RemoveThis<THooks> & RemoveThis<TCallbacks>; const configure = < TContext extends object, THooks extends Record<string, FunctionWithThis<FullCombinedContext<TContext, THooks, TCallbacks>>>, TCallbacks extends Record<string, FunctionWithThis<FullCombinedContext<TContext, THooks, TCallbacks>>> >(config: Config<TContext> & Hooks<THooks> & Callbacks<TCallbacks>) => { const result: Partial<FullCombinedContext<TContext, THooks, TCallbacks>> = { get data() { return config.context; } }; // 挂载钩子 Object.entries(config.hooks).forEach(([key, hook]) => { (result as any)[key] = (...args: any[]) => hook.call(result, ...args); }); // 挂载回调 Object.entries(config.callbacks).forEach(([key, callback]) => { (result as any)[key] = (...args: any[]) => callback.call(result, ...args); }); return result as FullCombinedContext<TContext, THooks, TCallbacks>; }; // 测试:钩子和回调可以互相调用 const engine = configure({ context: { foo: 12 }, hooks: { log() { console.log(this.data.foo); }, test(str: string) { this.log(); this.onTestFinish(str); // 调用回调 } }, callbacks: { onTestFinish(str: string) { console.log(`Test finished with: ${str}`); this.log(); // 调用钩子 } } }); engine.test("hello"); // 输出: // 12 // Test finished with: hello // 12
这里新增了TCallbacks泛型参数,同样让它自引用FullCombinedContext,确保回调的this也能访问钩子和上下文。同时在configure函数中把回调也挂载到result上,并绑定this为result。
补充说明
TypeScript对自引用泛型约束的支持是完全没问题的,核心思路是:
- 明确合并后的上下文类型,包含所有需要通过
this访问的成员 - 让钩子/回调的
this类型指向这个合并后的上下文 - 把钩子/回调的
this绑定到包含所有成员的实际对象(也就是result)
这样就能实现钩子间、钩子与回调间的互相调用,同时TypeScript能提供完整的类型提示。
内容的提问来源于stack exchange,提问作者Roman Koliada
相关产品推荐
相关产品推荐

