【问题标题】:TypeScript how to use stricter types for a child constructor?TypeScript 如何为子构造函数使用更严格的类型?
【发布时间】:2021-12-15 10:25:40
【问题描述】:

假设我有两个班级,一个Dog 和一个Animal 班级。在此示例中,Dog extends Animal。 现在假设我有两个额外的类(一个叫Parent,另一个叫Child)。在 TypeScript 中,我可以使用 ConstructorParameters 将我的子类的构造函数的参数键入为我的父类的构造函数的参数,如下所示:

class Parent {
  constructor(a: Animal, b: number, c: number) {}
}

class Child extends Parent {
  constructor(...args: ConstructorParameters<typeof Parent>) { // same as parent's arguments: a: Animal, b: number, c: number
    super(...args); // doesn't complain, as args matches the arguments for Parent ([a: Animal, b: number, c: number])
  }
}

在此示例中,new Child()new Parent() 将接受相同的参数类型。但是我如何键入 Child 构造函数来接受a 的更严格的类型,例如Dog

class Parent {
  constructor(a: Animal, b: number, c: number) {}
}

class Child extends Parent {
  //          v----- want to access a in `Child`'s constructor
  constructor(a: Dog, ...args: ConstructorParameters<typeof Parent>) {
    super(a, ...args); // <-- complains, I want to pass `a: Dog` and ...args (containing [b: number, c: number])
    a.bark(); // use specific Dog methods (that don't exist on Animal)
  }
}
// Using: 
// - new Child(new Animal(), 0, 0); should complain as Animal is not of type Dog
// - new Child(new Dog(), 0, 0); should work as Dog is of type Dog

上述方法不起作用,因为 Child 现在期望传递一个 Dog 实例,后跟 Parent 构造函数的三个参数。我正在考虑使用Omit&lt;&gt;Partial&lt;&gt; 上的 ConstructorParameters 删除预期的 a: Animal 类型,但似乎无法让它们工作。我也在寻找一种不需要将参数单独传递给super()调用的解决方案(即:如果我在Parent的构造函数中添加参数,我不需要将它们添加到Child) .

【问题讨论】:

    标签: javascript typescript class constructor subclass


    【解决方案1】:

    但是我如何键入Child 构造函数来接受a 的更严格类型,例如Dog

    这听起来是个坏主意,违反了Liskov substitution principle:人们应该能够在任何可以使用Parent 的地方使用Child

    因此我会推荐泛型:

    class Parent<T extends Animal> {
      constructor(a: T, b: number, c: number) {}
    }
    
    class Child extends Parent<Dog> {
      constructor(...args: ConstructorParameters<typeof Parent<Dog>>) {
        super(...args);
        const a = args[0];
        a.bark(); // use specific Dog methods (that don't exist on Animal)
      }
    }
    

    【讨论】:

    • 关于 Liskov 原则的要点。这里使用泛型很有意义 - 感谢 Bergi
    【解决方案2】:

    尝试条件类型

    type RestWithoutOne<K> = K extends [infer WithThis, ...infer WithRest] ? WithRest : never;
    
    class Child extends Parent {
      //          v----- want to access a in `Child`'s constructor
      constructor(a: Dog, ...args: RestWithoutOne<ConstructorParameters<typeof Parent>>) {
        const a = args[0];
        super(a, ...args); // <-- complains, I want to pass `a: Dog` and ...args (containing [b: number, c: number])
        a.bark(); // use specific Dog methods (that don't exist on Animal)
      }
    }
    

    Playground link

    【讨论】:

    • 哇,这看起来很棒,迈克。我不知道你可以写出这样复杂的类型。非常感谢
    猜你喜欢
    • 2020-11-26
    • 2021-05-30
    • 1970-01-01
    • 2022-06-25
    • 2021-09-21
    • 2018-09-21
    • 2022-01-26
    • 1970-01-01
    相关资源
    最近更新 更多