有没有一种方法可以键入TypeScript方法装饰器,以限制它可以装饰的方法类型?

问题描述 投票:0回答:2

我想编写一个TypeScript方法装饰器,该装饰器只能应用于具有某种类型的第一个参数的方法。这是我正在使用的代码库中的一种常见模式,用于传递具有数据库,指标,日志记录等句柄的请求上下文。我想编写一个在请求上下文中需要这些资源之一的装饰器,但是否则与请求上下文的形状无关。

这是一个程式化的示例:

interface MyResource {
    logMetricsEtc(...args: any): void;
}

interface HasResourceINeed {
    myResource: MyResource;
}

function myDecorator<TFn extends ((tContext: HasResourceINeed, ...rest: any) => any)>(
    _target: object,
    key: string | symbol,
    descriptor: TypedPropertyDescriptor<TFn>,
): TypedPropertyDescriptor<TFn> | void {
    const originalHandler = descriptor.value!;

    descriptor.value = function (this: any, context: HasResourceINeed, ...inputs: any) {
        context.myResource.logMetricsEtc(...inputs);

        return originalHandler.apply(this, [context, ...inputs]);
    } as TFn;
}

[在使用中,启用strictFunctionTypes时,此装饰器在应用于看上去合理的方法时会导致编译错误:

interface ServiceContext {
    myResource: MyResource;
    otherResource: {
        sendMessageEtc(msg: string): Promise<void>;
    };
}

class MyBusinessClass {
    // This causes a compile error, but shouldn't - the decorator will 
    // work here at runtime.
    @myDecorator
    async foo(context: ServiceContext, x: number): Promise<void> {

    }

    // This example MUST cause a compile error to prevent invalid 
    // usage - there's no resource available in the first arg for the
    // decorator to use.
    @myDecorator
    async bar(y: string): Promise<void> {

    }
}

不需要的编译错误如下所示:

Argument of type 'TypedPropertyDescriptor<(context: ServiceContext, x: number) => Promise<void>>' is not assignable to parameter of type 'TypedPropertyDescriptor<(tContext: HasResourceINeed, ...rest: any) => any>'.
  Types of property 'value' are incompatible.
    Type '((context: ServiceContext, x: number) => Promise<void>) | undefined' is not assignable to type '((tContext: HasResourceINeed, ...rest: any) => any) | undefined'.
      Type '(context: ServiceContext, x: number) => Promise<void>' is not assignable to type '(tContext: HasResourceINeed, ...rest: any) => any'.(2345)

我无法合理地关闭strictFunctionTypes。是否可以编写装饰器类型以接受foo但拒绝bar

typescript contravariance typescript-decorator
2个回答
1
投票

大概您希望myDecorator()的输入在修饰方法的第一个参数的类型R中是通用的,而不必在整个方法的Fn类型中都是通用的。这将允许您接受第一个参数是R的某些子类型的方法,而不是Fn的子类型的方法(这意味着根据方法参数的相反性,它们的参数必须为Rsupertypes)不是您要应用的约束)。

也许是这样?

function myDecorator<R extends HasResourceINeed>(
    _target: object,
    key: string | symbol,
    descriptor: TypedPropertyDescriptor<((tContext: R, ...rest: any) => any)>,
): TypedPropertyDescriptor<((tContext: R, ...rest: any) => any)> | void {
    const originalHandler = descriptor.value!;
    descriptor.value = function (this: any, context: R, ...inputs: any) {
        context.myResource.logMetricsEtc(...inputs);
        return originalHandler.apply(this, [context, ...inputs]);
    };
}

这似乎起作用:

class MyBusinessClass {

    @myDecorator // okay
    async foo(context: ServiceContext, x: number): Promise<void> {

    }

    @myDecorator // error
    async bar(y: string): Promise<void> {

    }
}

好的,希望能有所帮助;祝你好运!

Playground link to code


0
投票

您将需要使用Reflection来实现。

function Multiply(): any {
  return (target: any, propertyKey: string) => {
    const firstType = getPropertyParamTypes(target, propertyKey)[0].name;
    if (firstType !== "Number") {
      throw new Error(
        "Multiply  can only be used with methods whos first arg is a number"
      );
    }
  };
}

function getPropertyParamTypes(target: any, propertyKey: string): any[] {
  return Reflect.getMetadata("design:paramtypes", target, propertyKey);
}

class MyClass {
  constructor(private initial: string) {}

  @Multiply()
  public methodA(n: number): string {
    return `${this.initial}: ${n}`;
  }

  public methodB(s: string): string {
    return `${this.initial} - ${s}`;
  }
}

const c = new MyClass("msg");
console.log(c.methodA(2));
© www.soinside.com 2019 - 2024. All rights reserved.