如何防止使用错误的参数类型调用重载函数

How to prevent an overloaded function from being called with the wrong argument types

我想为一些 JS 代码生成 typescript 定义,这有点尴尬,因为有些 类 的静态方法与其父类的静态方法同名但类型签名不同。更多详情 here and here.

我的解决方案尝试是使用像这样的通用重载:

export class Parent {
    ...
    static new(arg1: string): Parent
}

export class Child extends Parent {
    ...
    static new(arg1: number, arg2: number): Child
    static new<T, V>(arg1: T): V
}

这可能会被 T 的任意类型调用泛型形式所滥用,从而导致未定义的行为。有没有办法防止这种情况发生? Typescript 坚持所有具有相同名称的重载具有相同的可见性,所以我不能将通用的重载设为私有。但是,如果没有办法限制对具有固定类型的签名之一的调用,那么我们似乎已经错过了重载的最佳好处之一。

无论您如何处理重载签名,TypeScript 已决定静态方法将从父 classes (as possibly implied by ES2015) 继承,因此您不能只扩展 class 并解决这个问题。至少直到并且除非 TS 发生变化。例如:

namespace Original {
  declare namespace Classes {
    class Parent {
      static method(arg1: string): Parent;
      parentProp: string;
    }

    class Child extends Parent {
      static method(arg1: number, arg2: number): Child;
      static method<T, V>(arg1: T): V;
      childProp: string;
    }
  }

  const child = new Classes.Child();
  const parent = new Classes.Parent();
  const alsoParent: typeof parent = new Classes.Child();

  const childConstructorMethod = Classes.Child.method;
  const parentConstructorMethod = Classes.Parent.method;

  // the following inheritance should hold
  const alsoParentConstructorMethod: typeof parentConstructorMethod = childConstructorMethod;
  // which leads to this even without generics
  alsoParentConstructorMethod("oopsie-daisy");
}

这与您的 classes 类似,您可以看到,无论您做什么,上面的代码最终都会让人们在子构造函数上调用 parent-signature 方法...因为可以将子静态方法分配给父静态方法类型的变量。这就是继承,它对您无法使用签名解决的事情施加了限制。


那你能做什么?我会考虑更改声明的类型,以便根本不使用 class 。请注意,这并不意味着 JS 不能使用 class;这只是意味着表示这种关系的最佳方式是使用您自己的类型和值。例如:

namespace PossibleFix {
  declare namespace Classes {
    interface Parent {
      parentProp: string;
    }
    export const Parent: {
      method(arg1: string): Parent;
      new (): Parent;
    };

    interface Child extends Parent {
      childProp: string;
    }

    export const Child: {
      method(arg1: number, arg2: number): Child;
      new (): Child;
    };
  }

  const child = new Classes.Child();
  const parent = new Classes.Parent();
  const alsoParent: typeof parent = new Classes.Child();

  const childConstructorMethod = Classes.Child.method;
  const parentConstructorMethod = Classes.Parent.method;

  // now there is no implied inheritance on the static side
  const alsoParentConstructorMethod: typeof parentConstructorMethod = childConstructorMethod;
  //    ~~~~~~~~~~~~~~~~~~~~~~~~~~~ <-- error! not assignable

}

这与最初的基于 class 的类型几乎相同。 class 有一个实例端和一个静态端。在这里,实例端变成了一个interface,静态端变成了一个导出的const变量,它既是构造函数(new():...)又具有方法。但是在这里,即使实例端继承(我们仍然有 Child extends Parent),静态端现在彼此无关。这允许您在子构造函数的方法上选择您想要的任何签名。

这最终会变得更加冗长,特别是如果您有生成原始声明的实际 TypeScript 代码,因为现在您必须想出一种方法来断言生成的代码符合后面的声明,并且它可能很乱。但是,它给了你更多的自由。希望能有所帮助。祝你好运!

Link to code