是否可以声明一个接受超类实例数组并返回最具体的子类类型的函数 [英] Is it possible to declare a function that accepts an array of superclass instances and returns the most specific subclass type

查看:23
本文介绍了是否可以声明一个接受超类实例数组并返回最具体的子类类型的函数的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'm writing a Typescript declarations file for my Javascript package.

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

My library has a function that accepts an array of superclass elements and returns the superclass.

function x(args: SuperClass[]): SuperClass

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

I would like to modify the return type of the method so that it returns the type of the most specific subclass element.

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

这可能吗?

推荐答案

(以下使用 TS3.1 :)

(Using TS3.1 in the following:)

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

This answer is so full of caveats that I feel weird even posting it. Plus I don't really think I understand your use case. But the type juggling involved in locating the most specific type in a list has piqued my curiosity. So here we are!

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

The following could only possibly work if the objects you pass to x contain at least one element that is an instanceof the constructor for every single other value passed. That means the class hierarchy has no forks in it (or at least none in the list of things you pass to x) and that it is an actual class hierarchy using prototypical inheritance.

这里是:

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<> 接受一个数组(或 tuple) 类型并返回作为所有其他元素的子类型的元素,如果有的话......否则它成为底部类型 从不.

The explanation is a bit involved as it uses a lot of conditional types, especially distributed ones which allows you to inspect union constituents. The effect is that Absorb<> takes an array (or tuple) type and returns the element which is a subtype of all the other elements, if there is one... otherwise it becomes the bottom type never.

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

In the x function I'm also using rest parameters instead of an array because it helps infer tuple types for the passed-in parameters.

让我们看看它是否有效:

Let's see if it works:

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() 工作:

I know you wanted it to be A, but you're not handing it any parameters. How can it return a value of type A when it doesn't have one? Oh well, we'll assume there's a value named a defined in an outer scope. You can modify the above to make a zero-parameter x() work:

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

这里有一些注意事项...如果您使用分叉的层次结构:

Here are some caveats... if you use hierarchies with forks:

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

可能还会发生其他疯狂事件.但这已经是我所能得到的.希望能帮到你.祝你好运!

And probably other craziness can happen also. But that's as close as I can get. Hope that helps you. Good luck!

这篇关于是否可以声明一个接受超类实例数组并返回最具体的子类类型的函数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆