我正在try 用TypeScrip编写一个函数,该函数接受一个元组数组作为参数,其中对于每个元组,第一个元素必须是第二个元素的父类型,或者换句话说,第二个元素必须扩展第一个元素.

例如:

function foo(x: any) {}

abstract class A {
  abstract foo(): any
}
class B extends A {
  foo() {
    throw new Error("Method not implemented.")
  }
}
abstract class C {
  abstract baz(): any
}
class D extends C {
  baz() {
    throw new Error("Method not implemented.")
  }
}

foo([
 [A, B],
 [C, D]
])

请记住,我希望能够将类本身作为值传递,而不是它们的实例(A.K不是new A(),而是A)

我试过了

function foo<A, B extends A>(x: Array<[A, B]>) {}

如果我传递一个元组的数组,它就会起作用,但一旦我添加另一个元组,它就不起作用了.

我也试着创造了这种类型

type ValidateTuples<T> = T extends Array<[any, any]>
  ? {
      [K in keyof T]: T[K] extends [infer A, infer B]
        ? B extends A
          ? T[K]
          : never
        : never
    }
  : never

type CT = ValidateTuples<[[A, B], [C, D]]>

这验证了一个类型是否是元组数组,其中第一个元素是第二个类型的父元素,如果有效,则返回一个类型,否则用never替换不正确的类型,但我不知道如何在函数中实际使用它.

我的问题是,如何用TypeScrip编写函数,使其只接受元组数组,其中元组的第二个元素必须扩展第一个元素?

推荐答案

一种方法是在实例类型的tuple typeT中创建foo()generic.所以如果你调用foo([[A, B], [C, D]]),那么泛型类型参数T就是[A, C].x的类型将是mapped type over that tuple,其中T的每个元素将被转换为具有您所关心的约束的一对类构造函数类型.

从概念上讲,这看起来像这样:

function foo<T extends object[]>(x: { [I in keyof T]: [
    abstract new (...args: any) => T[I],
    new (...args: any) => T[I]
}) { }

对于T个元组的每个数字索引I,T[I]实例类型在x类型的对应第I个元素中使用两次.它是该对的第一个元素中的实例类型(可能是abstract),也是该对的第二个元素中的类构造函数的实例类型.

请注意,这并不完全强制第二个构造函数扩展第一个构造函数,只是它们都是构造和实例的同一类型.但如果我们确保T[I]只是从这对元素中的第一个元素而不是第二个元素推断出来的,这将自然而然地发生(好吧,对于"扩展"的某些定义而言).所以我们想说,第二个T[I]只存在于类型checking,而不是类型inference.有一个长期的功能要求,即用大约NoInfer<T>个操作员在microsoft/TypeScript#14829的位置上标记non-inferential type parameter usage.这方面没有本机操作符,但GitHub问题描述了一些实现它的方法,这些方法适用于某些用例.在这里,我将在这一期中写下其中一个建议:

type NoInfer<T> = [T][T extends any ? 0 : never];

现在我们有了

function foo<T extends object[]>(x: { [I in keyof T]: [
    abstract new (...args: any) => T[I],
    new (...args: any) => NoInfer<T>[I]
}) { }

我们已经很接近了.现在唯一的问题是,编译器可能不想将T推断为元组,因为数组文字往往被推断为无序数组而不是元组.为了向编译器提示我们更喜欢元组,我们可以将x类型包装在variadic tuple type([...+])中:

function foo<T extends object[]>(x: [...{ [I in keyof T]: [
    abstract new (...args: any) => T[I],
    new (...args: any) => NoInfer<T>[I]]
}]) { }

好的,现在让我们测试一下:

foo([[A, B], [C, D]]); // okay
// function foo<[A, C]>(x: ⋯): void

根据需要推断出正确的调用.不正确的调用应该会产生错误:

foo([[A, D]]) // error
// ----> ~
// Type 'typeof D' is not assignable to type 'new (...args: any) => A'.
// function foo<[A]>(x: ⋯): void

在这里,编译器将T推断为[A],由于D不是A兼容对象的构造函数,因此会出现警告.您还会在此处收到警告:

foo([[A, B], [D, C]]) // error
// ------------> ~
// Type 'typeof C' is not assignable to type 'new (...args: any) => D'.
// Cannot assign an abstract constructor type to a non-abstract constructor type.
// function foo<[A, D]>(x: ⋯): void

但这仅仅是因为foo()不允许每对元素中的第二个元素是abstract.您对CD类的定义并没有将它们彼此区分开来.典型的例子:

class B2 { foo() { } }
foo([[A, B2]]) // this is okay
// function foo<[A]>(x: ⋯): void

这不是错误,因为B2的实例等于structurally compatible加上A,即使B2的声明中没有extends子句.TypeScrip的类型系统主要是 struct 化的,因此它不会识别或关心BB2之间的任何差异.如果您需要真正关心的行为,您将不得不反对类型系统和/或使用各种技巧,例如包含private or protected class members.这就是为什么我之前说过,这取决于您对"扩展"的定义,这是否足够.希望does就足够了,但如果不够,关于 struct 类型系统中nominal types的近似值的讨论可能就不在这里了.

Playground link to code

Javascript相关问答推荐

为什么我无法使用useMemo()停止脚本渲染?

更新Reduxstore 中的状态变量,导致整个应用程序出现不必要的重新渲染

在HTML中使用脚本变量作为背景图像源

当promise 在拒绝处理程序被锁定之前被拒绝时,为什么我们会得到未捕获的错误?

如何为GrapesJS模板编辑器创建自定义撤销/重复按钮?

使用useup时,React-Redux无法找到Redux上下文值

Angular中计算信号和getter的区别

单击ImageListItemBar的IconButton后,在Material—UI对话框中显示图像

当试图显示小部件时,使用者会出现JavaScript错误.

如何使用JavaScript拆分带空格的单词

如何在使用rhandsontable生成表时扩展数字输入验证?

第二次更新文本输入字段后,Reaction崩溃

如何在Angular拖放组件中同步数组?

在数组中查找重叠对象并仅返回那些重叠对象

使用VUE和SCSS的数字滚动动画(&;内容生成)

如何在我的Next.js项目中.blob()我的图像文件?

Vaadin定制组件-保持对javascrip变量的访问

如何使用基于promise (非事件emits 器)的方法来传输数据?

如何为仅有数据可用的点显示X轴标签?

ReferenceError:无法在初始化之前访问setData