TypeScript函数装饰器反向类型推断失败输出为unknown如何修复
问题原因
你的类型推断失效核心是TypeScript泛型参数的默认推导顺序和装饰器执行顺序不匹配:
- 原有
decorate重载的泛型参数列表中,和最终handler直接相关的Input/Output位置靠后,TS会优先推断前面的装饰器相关泛型,此时无有效输入源会被推断为unknown,导致后续类型链路断裂 addToInput/addToOutput中未直接参与入参的泛型参数,无法被从左到右的默认推断逻辑识别,需要依赖反向推导链路
修复方案
仅需要调整decorate所有重载的泛型参数顺序,将和最右侧handler绑定的Input/Output放在泛型列表最前面,让TS优先从handler推断出这两个基础类型,再反向推导前面所有装饰器的类型:
type Merge<A, B> = Omit<A, keyof B> & B; // Context是存储输入数据的对象 type Handler<I, O> = ( context: I ) => Promise<O>; // 包装Handler的类型 type Decorator<I, NI, NO, O> = ( next: Handler<NI, NO> ) => Handler<I, O>; // 调整泛型顺序:将Input/Output放在最前面,优先从最右的handler推断 function decorate<Input, Output>( handler: Handler<Input, Output> ): Handler<Input, Output>; function decorate<Input, Output, A, Z>( mw1: Decorator<A, Input, Output, Z>, handler: Handler<Input, Output> ): Handler<A, Z>; function decorate<Input, Output, A, B, Y, Z>( mw1: Decorator<A, B, Y, Z>, mw2: Decorator<B, Input, Output, Y>, handler: Handler<Input, Output> ): Handler<A, Z>; function decorate<Input, Output, A, B, C, X, Y, Z>( mw1: Decorator<A, B, Y, Z>, mw2: Decorator<B, C, X, Y>, mw3: Decorator<C, Input, Output, X>, handler: Handler<Input, Output> ): Handler<A, Z>; function decorate( ...handlers: Function[] ) { return handlers.reduceRight((acc, h) => acc ? h(acc) : h) } interface InputContext { x: number; } interface OutputContext { a: string; } // 可正常推断为Handler<InputContext, OutputContext> const myHandler0 = decorate( async ({ x }: InputContext): Promise<OutputContext> => { return { a: `x = ${x}` }; } ); myHandler0({ x: 5 }) .then(o => console.log(`myHandler0: output = ${JSON.stringify(o)}`)) .catch(e => console.error(`myHandler0: `, e)); // 基础空装饰器,不做任何操作 const passthrough = <I, O>(): Decorator<I, I, O, O> => next => context => next(context); // 可正常推断为Handler<InputContext, OutputContext> const myHandler1 = decorate( passthrough(), async ({ x }: InputContext): Promise<OutputContext> => { return { a: `x = ${x}` }; } ); myHandler1({ x: 5 }) .then(o => console.log(`myHandler1: output = ${JSON.stringify(o)}`)) .catch(e => console.error(`myHandler1: `, e)); // 可修改输入/输出类型的高阶装饰器 const addToInput = <I, F, O>(factory: (context: I) => F): Decorator<I, Merge<I, F>, O, O> => next => context => next({ ...context, ...factory(context) }); const addToOutput = <I, O, F>(factory: (context: O) => F): Decorator<I, I, O, Merge<O, F>> => next => context => next(context).then(c => ({ ...c, ...factory(c) }) as Merge<O, F>); // 现在可正确推断为Handler<InputContext, { a: string, b: boolean }> const myHandler2 = decorate( addToInput((_c: InputContext) => ({ y: 10 })), addToOutput(_r => ({ b: true })), async ({ x, y }): Promise<OutputContext> => { // x和y都可以正常推断! return { a: `x = ${x + y}` }; } ); myHandler2({ x: 5 }) .then(o => console.log(`myHandler2: output = ${JSON.stringify(o)}`)) .catch(e => console.error(`myHandler2: `, e)); // 现在可正确推断为Handler<InputContext, { a: string, b: boolean }> const myHandler3 = decorate( addToInput((_c: InputContext) => ({ y: 10 })), passthrough(), addToOutput(_r => ({ b: true })), async ({ x, y }): Promise<OutputContext> => { // x和y都可以正常推断! return { a: `x = ${x + y}` }; } ); myHandler3({ x: 5 }) .then(o => console.log(`myHandler3: output = ${JSON.stringify(o)}`)) .catch(e => console.error(`myHandler3: `, e));
扩展说明
如果需要支持任意个数的装饰器,可以用TS 4.0+的元组递归推断代替固定参数重载,不需要手动写每个参数个数的重载版本。
内容的提问来源于stack exchange,提问作者spyke
相关产品推荐
相关产品推荐

