【问题标题】:Using class decorators, can I get the type of the class-type instance?使用类装饰器,我可以获得类类型实例的类型吗?
【发布时间】:2016-05-24 05:34:40
【问题描述】:

考虑一下failing example

function DecorateClass<T>(instantiate: (...params:any[]) => T){
    return (classTarget:T) => { /*...*/ }
}

@DecorateClass((json:any) => {
    //Purely example logic here, the point is that it have to return
    //an instance of the class that the decorator runs on.
    var instance = new Animal();
    instance.Name = json.name;
    instance.Sound = json.sound;
    return instance;
})
class Animal {
    public Name:string;
    public Sound:string;
}

这里我想限制装饰器中的匿名函数始终返回相关类的实例,但上述方法不起作用,因为 T 实际上是 typeof Animal 而不是 Animal

在一个泛型函数中,我是否可以从 typeof Animal 类型中获取类型 Animal 而不会像显式定义所有类型(如 function DecorateClass&lt;TTypeOfClass, TClass&gt;(...))那样冗长而烦人?

不幸的是,不支持在通用语法中使用 typeof,这是我试图让编译器理解我想要什么的最佳选择:

function DecorateClass<T>(instantiate: (json:any) => T){
    return (classTarget:typeof T) => { /*...*/  } // Cannot resolve symbol T
}

【问题讨论】:

  • 乍一看,这似乎是一个XY problem。您究竟想要完成什么?似乎您正在将 JSON 反序列化为类型实例,但无法确定。
  • @JohnWhite 不,这只是一个基本示例。我想要完成的是在装饰器函数的类型中拥有类实例的类型,而无需显式定义它。这种结构将应用于数百个类,我希望将装饰器的内部输入到类的实例中,我真的不喜欢必须明确定义实例类型的想法装饰器运行的类。

标签: typescript


【解决方案1】:

编辑

事实证明,您的要求是完全可能的。我添加了new answer,但也将把它留在这里,因为它可能包含对某人有价值的信息。这个答案建议一个运行时解决方案,新的一个建议一个编译时解决方案。


我想说你最好的选择是运行时类型检查,因为你在装饰器函数中拥有正确的类型:

function DecorateClass(instantiate: (...params: any[]) => any) {
    return (classTarget: Function) => {
        var instance = instantiate(/*...*/);

        if (!(instance instanceof classTarget)) {
            throw new TypeError();
        }

        // ...
    }
}

这不会产生编译时类型安全性。

【讨论】:

    【解决方案2】:

    请稍等片刻...

    最近我需要一个函数的类型定义,该函数接受一个类作为参数,并返回该类的 instance。当我想出solution 时,我很快就想到了这个问题。

    基本上,使用 newable 类型可以在类与其实例之间建立关系,这可以准确而完美地回答您的问题:

    function DecorateClass<T>(instantiate: (...args: any[]) => T) {
        return (classTarget: { new(...args: any[]): T }) => { /*...*/ }
    }
    

    说明

    在 TypeScript 中,任何给定的可新类型都可以使用以下签名来定义:

    new(...args: any[]): any
    

    这类似于一个新的类型(构造函数),它可能接受也可能不接受参数并返回any(实例)。但是,没有什么说返回的一定是any——它也可以是泛型类型。

    由于我们在泛型类型参数中准确地拥有了从构造函数返回的内容(通过类型推断应用装饰器的类),我们可以使用它来定义传入回调函数的返回类型。

    我已经测试了装饰器,它似乎按预期工作:

    @DecorateClass((json: any) => {
        return new Animal(); // OK
    })
    @DecorateClass((json: any) => {
        return Animal; // Error
    })
    @DecorateClass((json: any) => {
        return "animal"; // Error
    })
    class Animal {
        public Name: string;
        public Sound: string;
    }
    

    这实际上使我之前的 answer 无效。


    编辑:继承

    当涉及到继承时(例如:派生类型将从instantiate 返回),可分配性似乎被翻转了:您可以返回 base 类型,但不能返回 派生类型。

    这是因为在泛型类型推断期间,instantiate 的返回类型优先于 classTarget 的“返回”类型。以下问题检查了这个确切的问题:

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-06-26
      • 1970-01-01
      • 2011-03-27
      • 2018-10-14
      相关资源
      最近更新 更多