首页 > 解决方案 > 是否可以声明一个接受超类实例数组并返回最具体的子类类型的函数

问题描述

我正在为我的 Javascript 包编写一个 Typescript 声明文件。

我的库有一个函数,它接受一组超类元素并返回超类。

function x(args: SuperClass[]): SuperClass

我想修改方法的返回类型,使其返回最具体的子类元素的类型。

function x(args: SuperClass[]): TypeOfMostSpecificArg(args)

例如

interface A {}
interface B extends A {}
interface C extends B {}
let a: A, b: B, c: C
x([]) // should have return type A
x([a, b, a]) // should have return type B
x([a, b, c]) // should have return type C

这可能吗?

标签: typescripttypescript-typings

解决方案


(以下使用TS3.1:)

这个答案充满了警告,以至于即使发布它我也感到很奇怪。另外,我真的不认为我了解您的用例。但是在列表中查找最具体的类型所涉及的类型杂耍激起了我的好奇心。所以我们来了!

如果您传递给的对象至少包含一个元素,该元素是传递的每个其他值的构造函数,则以下内容可能才有效。这意味着类层次结构中没有分叉(或至少在您传递给的事物列表中没有),并且它是使用原型继承的实际类层次结构。xinstanceofx

开始:

type NotExtendsAll<T, U> = U extends any ? [T] extends [U] ? never : unknown : never;
type AbsorbUnion<T> = [T] extends [infer U] ? U extends any ? 
  NotExtendsAll<U, T> extends never ? U : never : never : never;
type Absorb<T extends any[]> = AbsorbUnion<{ [K in keyof T]: [T[K]] }[number]>[0];
function x<T extends any[]>(...args: T): Absorb<T> extends never ? undefined : Absorb<T>;
function x(...args: any[]): any {
    return args.find(a => (args.every(b => a instanceof b.constructor)));
}

解释有点复杂,因为它使用了很多条件类型,尤其是允许您检查工会成分的分布式类型。效果是Absorb<>接受一个数组(或元组)类型并返回作为所有其他元素的子类型的元素,如果有的话......否则它成为底部类型never

x函数中,我还使用了剩余参数而不是数组,因为它有助于推断传入参数的元组类型。

让我们看看它是否有效:

class A { a: string = "a" }
class B extends A { b: string = "b" }
class C extends B { c: string = "c" }
let a = new A();
let b = new B();
let c = new C();

const aaa = x(a, a, a); // type is A, returns a at runtime
const aba = x(a, b, a); // type is B, returns b at runtime
const abc = x(a, b, c); // type is C, returns c at runtime

看起来不错,我想。

现在,这不起作用:

const none = x(); // type is never, returns undefined at runtime

我知道你希望它是A,但你没有给它任何参数。A如果没有类型值,它如何返回?哦,好吧,我们假设a在外部范围中定义了一个名为的值。您可以修改上述内容以使零参数x()工作:

function otherX<T extends A[]>(...args: T): Absorb<T> extends never ? A : Absorb<T>;
function otherX(...args: A[]): A {
    return args.find(z => (args.every(b => z instanceof b.constructor))) || a;
}

const none = otherX(); // type is A, returns a at runtime
const otherAba = otherX(a, b, a); // type is B, returns B at runtime
const otherAbc = otherX(a, b, c); // type is C, returns C at runtime

这里有一些警告......如果你使用带有分叉的层次结构:

class D extends A { d: string = "d" }
let d = new D();
const whoops = x(a, b, c, d); // type is undefined, returns undefined at runtime
const alsoWhoops = otherX(b, c, d); // type is A, returns a at runtime

如果您使用非类实例:

const huh = x("a","b","c"); // type is supposedly string, returns undefined at runtime

并且可能还会发生其他疯狂事件。但这是我能得到的最接近的。希望对您有所帮助。祝你好运!


推荐阅读