网站搜索

TypeScript:无法解析通用类装饰器的签名


我无法理解为什么在这种情况下 TypeScript 需要为 Child2 和 Child3 显式定义泛型类型:

abstract class Base {
    public static A: string = "Fallback_A";
    public DoSmthWithClassName(): string {
        return "Fallback_DoSmth";
    }

    constructor(...args: any[]); // overload for type consistency with children
    constructor(x: string)
    { }
}

// typeof any non-abstract child of Base
type BaseType = typeof Base & (new(...args: any[]) => Base);

// decorator, modifies methods and static properties
function ClassDecorator<T extends BaseType>(valueA: string): (value: T) => T {
    return (value: T) => {
        value.prototype.DoSmthWithClassName = () => value.name + ".DoSmth." + value.A;
        value.A = value.name + valueA;
        return value;
    }
}

@ClassDecorator("Foo") // OK
class Child0 extends Base {

}

@ClassDecorator("Foo") // OK
class Child1 extends Base {
    constructor(x: number) {
        super(x.toString());
    }
}

@ClassDecorator("Foo") // Unable to resolve...
class Child2 extends Base {
    static X: number = 0;
}

@ClassDecorator<typeof Child3>("Foo") // OK
class Child3 extends Base {
    static X: number = 0;
}

答案#1

问题在于 TS 无法从唯一的参数 valueA 推断出 T 的类型。你想要的是在内部/返回的函数上使用泛型参数:

// decorator, modifies methods and static properties
function ClassDecorator(valueA: string) {
    return function <T extends BaseType>(value: T): T {
        value.prototype.DoSmthWithClassName = () => value.name + ".DoSmth." + value.A;
        value.A = value.name + valueA;
        return value;
    }
}

你的版本没有对 Child0 和 Child1 造成任何问题,因为它们在结构上与基础版本完全相同。

答案#2

回答已经太晚了,我不知道这是否仍然符合需求。不过,在 ClassDecorator 内不需要通用参数的情况下,你仍然可以用其中的 'static X' 实例化 Child2,并通过 Child2.X 访问 X。

这是因为一个没有构造函数的类装饰器就像一个没有船的经验丰富的水手,它需要一个类型,以便 Typescript 可以确定将其编译为一个函数。

一个最佳的解决方案是在 ClassDecorator 之后反映它,

const Reflected = (value:string) : ClassDecorator => {
  return reflected => { 
    // `reflected` here is `Type<Foo>`, not `Foo`
    console.log(`The reflected:  ${reflected}`)
  };
};

而且你可以立刻使用它,因为 @Reflected("Foo") 不是一个新实例,

//@ClassDecorator("Foo") // Unable to resolve...
@Reflected("Foo")
class Child2 extends Base {
  public static X: number = 0;
  
}

我能想到的一些原因:

  • 一个类,基本上在运行时是一个 JavaScript 函数,是非常通用的,但一个常量实例不是(关注类型),这就是为什么我们需要一个 ClassDecorator 来进行“依赖注入”和其他好处。在 TypeScript 中,我们需要一个“被装饰”的实例类型,而不是设计时的实例本身,并且它在编译时提供正确的类型。

TS 游乐场

附注:如果你正在使用 'reflect-metadata' 包,你可以通过 console.log(Reflect.getMetadata('design:paramtypes', reflected)); 查看更多。我还没有看到任何副作用,如果发现任何问题,我会更新。