我有一个类,它包含一个成员,该成员是一个函数,它接受类的一个实例:

class Super {
    public member: (x: Super) => void = function(){}
    use() {const f = this.member; f(this)}
}

但我希望成员是逆变量;具体地说,我希望允许子类实例接受member个值,这些值是接受特定子类的函数,即:

class Sub extends Super {
    method() {}
}

const sub = new Sub();
sub.member = function(x: Sub) {x.method()};

但台积电相当正确地抱怨道:

Type '(x: Sub) => void' is not assignable to type '(x: Super) => void'.
  Types of parameters 'x' and 'x' are incompatible.
    Property 'method' is missing in type 'Super' but required in type 'Sub'.

如何在子类中声明member,使其成为接受协变(而不是逆变)参数类型的函数?


我试过的是:

  • 我知道如果我使用方法语法(member(s: Super) {/* ... */})声明member,那么它将是双变量的,但这对member可能是函数集合的情况没有帮助(例如,在我的实际代码中,member的类型是这样的函数的字典:{[name: string]: (/*...*/, s: Super) => /*...*/}).

  • 我try 使用更严格的签名重新声明Sub中的member:

    class Sub extends Super {
        public member: (x: Sub) => void = function(x){x.method()};
        method() {}
    }
    

    但台积电坚决不让我把枪对准我的脚:

    Property 'member' in type 'Sub' is not assignable to the same property in base type 
    'Super'.
      Type '(x: Sub) => void' is not assignable to type '(x: Super) => void'.
        Types of parameters 'x' and 'x' are incompatible.
          Property 'method' is missing in type 'Super' but required in type 'Sub'.
    
  • 我理解这typescript now supports in and out modifiers on templates to denote co/contravariance个,但我不确定它们是否适用,也不确定如何将Super变成一个适当的模板声明.

  • 我不想关闭strictfunctionTypes,因为它通常很有用,我不想强迫这个库的用户关闭它,以便在子类实例上分配给.member.

  • 作为最后的手段,我可以将赋值转换为as (x: Super) => void, 但这消除了防止分配给错误的子类的保护,例如:

    class Sub1 extends Super {
        method1() {}
    }
    class Sub2 extends Super {
        method2() {}
    }
    
    const sub1 = new Sub1();
    sub1.member = function(x: Sub2) {x.method2()} as (x: Super) => void;
    

    被TSC接受,但在运行时失败.

  • 判断相关的问题,我看到a similar question involving interfaces而不是子类,但它还没有正式的答案,我也不完全理解注释中链接的片段;它们似乎依赖于能够完全枚举所有子类型,这不适合我的情况,因为可能有任意数量的(子)*子类.

推荐答案

看起来您可能需要polymorphic this type,它的作用类似于隐式generic类型参数,它总是被约束到"Current"类.因此,在Super类主体中,this类型引用"Super的某个子类型",而在Sub类主体中,它引用"Sub的某个子类型".对于Super中的instances,this类型将实例化为Super,而对于Sub,它将实例化为Sub.

也就是说,在类体内部,this的作用类似于泛型参数,而在类体之外,它的作用就像用与当前对象类型对应的类型实参指定了该参数一样.


这为您的示例代码提供了所需的行为:

class Super {
    public member: (x: this) => void = function () { } 
    use() { const f = this.member; f(this) }
}


class Sub extends Super {
    method() { }
}

const sub = new Sub();
sub.member = function (x: Sub) { x.method() }; // okay

看上go 不错.


请注意,您可以通过使用泛型explicitly(使用让人想起Java的recursive, F-bounded约束)来模拟此行为:

class Super<T extends Super<T>> { 
    public member: (x: T) => void = function () { }
    use(this: T) { const f = this.member; f(this) } 
}

class Sub extends Super<Sub> {
    method() { }
}

const sub = new Sub();
sub.member = function (x: Sub) { x.method() };

如果this种类型本身不能满足您的需求,它不那么美观,但会给您更多的灵活性.

Playground link to code

Typescript相关问答推荐

创建一个将任意命名类型映射到其他类型的TypScript通用类型

具有映射返回类型的通用设置实用程序

如何在TypeScript对象迭代中根据键推断值类型?

我们过go 不能从TypeScript中的联合中同时访问所有属性?

如何在TypeScrip面向对象程序设计中用方法/属性有条件地扩展类

为什么我的Set-Cookie在我执行下一次请求后被擦除

获取函数中具有动态泛型的函数的参数

如何使我的函数专门针对联合标记?

找不到财产的标准方式?

如何过滤文字中的类对象联合类型?

如何在React组件中指定forwardRef是可选的?

作为函数参数的联合类型的键

如何在Nextjs路由处理程序中指定响应正文的类型

如何通过转换器类继承使用多态将对象从一种类型转换为另一种类型

Typescript泛型调用对象';s方法

如何根据Typescript 中带有泛型的对象嵌套键数组获取数组或对象的正确类型?

我可以使用对象属性的名称作为对象中的字符串值吗?

如何使用 Angular 确定给定值是否与应用程序中特定单选按钮的值匹配?

为什么 TypeScript 类型条件会影响其分支的结果?

对象只能使用 Typescript 中另一个对象的键