如何通过TypeScript中的类型描述元组数组中元组元素之间的关系? [英] How to describe the relationship between elements of tuple in array of tuples via types in TypeScript?

查看:96
本文介绍了如何通过TypeScript中的类型描述元组数组中元组元素之间的关系?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想通过TypeScript中的类型描述元组数组中元组元素之间的关系.

I want to describe the relationship between elements of tuple in array of tuples via types in TypeScript.

这可能吗?

declare const str: string;
declare const num: number;
function acceptString(str: string) { }
function acceptNumber(num: number) { }

const arr /*: ??? */ = [
    [str, acceptString],
    [num, acceptNumber],
    [str, acceptNumber], // should be error
];

for (const pair of arr) {
    const [arg, func] = pair;
    func(arg); // should be no error
}

真实示例:推荐答案

您基本上是在要求我打电话给我的东西相关的记录类型,目前在TypeScript中尚无直接支持.即使您可以说服编译器在创建此类记录时发现错误,也无法真正使用它来验证类型安全性.

You're basically asking for something I've been calling correlated record types, for which there is currently no direct support in TypeScript. Even in the case where you can convince the compiler to catch errors in creating such records, it is not really equipped to verify type safety when using one.

实现此类类型的一种方法是使用存在定量的泛型,其中TypeScript 当前不直接支持.如果是这样,您可以将数组描述为:

One way to implement such types would be with existentially quantified generics which TypeScript does not currently support directly. If it did, you'd be able to describe your array as something like:

type MyRecord<T> = [T, (arg: T)=>void];
type SomeMyRecord = <exists T> MyRecord<T>; // this is not valid syntax
type ArrayOfMyRecords = Array<SomeMyRecord>;

下一个最好的办法是允许ArrayOfMyRecords本身是一种通用类型,其中,数组的每个元素都使用类似的T值进行强类型化,并使用一个辅助函数来推断更强的类型:

The next best thing might be to allow ArrayOfMyRecords to itself be a generic type in which each element of the array is strongly typed with its analogous T value, and a helper function to infer the stronger type:

type MyRecord<T> = [T, (arg: T) => void];

const asMyRecordArray = <A extends any[]>(
  a: { [I in keyof A]: MyRecord<A[I]> } | []
) => a as { [I in keyof A]: MyRecord<A[I]> };

这使用映射类型的推断映射的元组.让我们看看它的作用:

This uses infererence from mapped types and mapped tuples. Let's see it in action:

const arr = asMyRecordArray([
  [str, acceptString],
  [num, acceptNumber],
  [str, acceptNumber] // error
]);
// inferred type of arr:
// const arr:  [
//   [string, (arg: string) => void], 
//   [number, (arg: number) => void], 
//   [string, (arg: string) => void]
// ]

让我们解决这个问题:

const arr = asMyRecordArray([
  [str, acceptString],
  [num, acceptNumber],
  [str, acceptString] 
]);
// inferred type of arr:
// const arr:  [
//   [string, (arg: string) => void], 
//   [number, (arg: number) => void], 
//   [string, (arg: string) => void]
// ]

因此足以定义arr.但是,现在看看当您对其进行迭代时会发生什么:

So that works well enough to define arr. But now look what happens when you iterate over it:

// TS3.3+ behavior
for (const pair of arr) {
  const [arg, func] = pair; 
  func(arg); // still error!
}

这是缺少相关记录支持的地方,这会伤到您.在TypeScript 3.3中,添加了对调用函数类型的联合的支持,但是该支持确实可以不能解决这个问题,即:编译器将func视为函数的 union ,这些函数与>类型完全 unorrelated 完全无关.当您调用它时,编译器决定它只能安全​​地接受类型为string & number的参数,而arg不是(也不是任何实际值,因为string & number折叠为never).

This is where the lack of support for correlated records burns you. In TypeScript 3.3, support was added for calling unions of function types, but that support does not touch this issue, which is: the compiler treats func as a union of functions which is completely uncorrelated with the type of arg. When you call it, the compiler decides that it can only safely accept arguments of type string & number, which arg is not (nor is any actual value, since string & number collapses to never).

因此,如果您采用这种方式,将会发现您需要

So if you go this way you'll find you need a type assertion to calm the compiler down:

for (const pair of arr) {
    const [arg, func] = pair as MyRecord<string | number>;
    func(arg); // no error now
    func(12345); // no error here either, so not safe
}

一个人可能会认为这是您可以做的最好的事情,然后把它留在那里.

One might decide this is the best you can do and to leave it there.

现在,有一种方法可以在TypeScript中编码存在类型,但是它涉及类似于Promise的控件反转.不过,在走那条路线之前,请问问自己:当您不知道T时,您将如何实际使用MyRecord<T> ?您可以做的唯一合理的事情是将其第一个元素称为第二个元素.如果是这样,您可以给出一种更具体的方法,只需做到这一点而无需跟踪T:

Now, there is a way to encode existential types in TypeScript, but it involves a Promise-like inversion of control. Before we go down that route, though, ask yourself: what are you going to actually do with a MyRecord<T> when you don't know T ? The only reasonable thing you can do is to call its first element with its second element. And if so, you can give a more concrete method that just does that without keeping track of T:

type MyRecord<T> = [T, (arg: T) => void];
type MyUsefulRecord<T> = MyRecord<T> & { callFuncWithArg(): void };

function makeUseful<T>(arg: MyRecord<T>): MyUsefulRecord<T> {
    return Object.assign(arg, { callFuncWithArg: () => arg[1](arg[0]) });
}

const asMyUsefulRecordArray = <A extends any[]>(
    a: { [I in keyof A]: MyUsefulRecord<A[I]> } | []
) => a as { [I in keyof A]: MyUsefulRecord<A[I]> };

const arr = asMyUsefulRecordArray([
    makeUseful([str, acceptString]),
    makeUseful([num, acceptNumber]),
    makeUseful([str, acceptString])
]);

for (const pair of arr) {
    pair.callFuncWithArg(); // okay!
}

您的真实示例可以进行类似的修改:

Your real-world example could be modified similarly:

function creatify<T, U>(arg: [new () => T, new (x: T) => U]) {
    return Object.assign(arg, { create: () => new arg[1](new arg[0]()) });
}

const map = {
    [Type.Value1]: creatify([Store1, Form1]),
    [Type.Value2]: creatify([Store2, Form2])
};

function createForm(type: Type) {
    return map[type].create();
}

在TypeScript中模拟存在性类型与上述类似,不同之处在于它允许您对MyRecord<T>进行绝对任何操作,如果您不知道T则可以执行.由于在大多数情况下,这只是一小部分操作,所以直接直接支持这些操作通常会更容易.

Emulating existential types in TypeScript is similar to the above except that it allows you to do absolutely anything to a MyRecord<T> that can be done if you don't know T. Since in most cases this is a small set of operations, it's often easier to just support those directly instead.

好的,希望能有所帮助.祝你好运!

Okay, hope that helps. Good luck!

这篇关于如何通过TypeScript中的类型描述元组数组中元组元素之间的关系?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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