如何通过打字稿中的类型描述元组阵列中元组元素之间的关系



我想通过打字稿中的类型描述元组中元组元素之间的关系。

这可能吗?

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中没有直接支持。即使您可以说服编译器在创建此类记录中遇到错误的情况下,使用一种记录也没有真正的验证类型安全性。

实施此类类型的一种方法是使用量化的仿制药,哪些Typescript当前不直接支持。如果这样做,您将可以将您的数组描述为:

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

下一个最好的事情可能是允许ArrayOfMyRecords本身是一种通用类型,其中数组的每个元素都用其类似的T值键入强烈键入,而辅助功能可以推断出更强的类型:

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

这使用了映射类型和映射的元素的推断。让我们看一下它:

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。但是现在看,当您迭代它时会发生什么:

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

这是缺乏对相关记录的支持会使您燃烧的地方。在打字稿3.3中,添加了用于调用功能类型工会的支持,但是该支持并没有遇到此问题,即:编译器将func视为a union 的功能,这完全是不相关的<</em>与arg的类型。当您调用它时,编译器决定它只能安全接受string & number类型的参数,arg不是(也不是任何实际值,因为string & number倒入never(。

因此,如果您这样走,您会发现您需要一种类型的断言才能使编译器平静下来:

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
}

一个人可能会决定这是您可以做的最好的,并将其留在那里。


现在,有一种方法可以在打字稿中编码存在类型,但是它涉及Promise的控制倒置。不过,在我们沿着这条路线走下去之前,请问自己:当您不知道T时,您将使用MyRecord<T>实际上 do ?您可以做的唯一合理的事情是用其第二个元素调用其第一个元素。如果是这样,您可以给出一种更具体的方法,该方法而无需跟踪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!
}

您的实际示例可以类似地修改:

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。由于在大多数情况下,这是一小部分操作,因此通常更容易直接支持这些操作。


好吧,希望有帮助。祝你好运!

最新更新