是否可以声明一个函数,该函数接受超类实例的数组并返回最特定的子类类型

时间:2018-10-25 18:20:39

标签: typescript typescript-typings

我正在为我的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

这可能吗?

1 个答案:

答案 0 :(得分:2)

(在下面使用TS3.1:)

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

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

在这里:

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)));
}

由于使用了很多conditional types,特别是distributed的解释,因此涉及到的解释有些复杂,允许您检查工会组成部分。结果是Absorb<>接受数组(或tuple)类型并返回元素,该元素是所有其他元素的子类型(如果有的话...),否则它将成为底部类型{{ 3}}。

x函数中,我还使用了rest参数而不是数组,因为它有助于never传入的参数。

让我们看看它是否有效:

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

可能还会发生其他疯狂现象。但这是我所能接近的。希望对您有帮助。祝你好运!