你基本上是在询问我一直在打电话的事情相关记录类型 https://github.com/Microsoft/TypeScript/issues/30581,目前 TypeScript 中没有直接支持。即使您可以说服编译器在创建此类记录时捕获错误,但在使用记录时它并没有真正具备验证类型安全性的能力。
实现此类类型的一种方法是存在量化的泛型 https://en.wikipedia.org/wiki/Type_system#Existential_types哪个 TypeScript目前不直接支持 https://github.com/Microsoft/TypeScript/issues/14466。如果是这样,您就可以将您的数组描述为:
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]> };
这使用从映射类型推断 https://www.typescriptlang.org/docs/handbook/advanced-types.html#inference-from-mapped-types and 映射元组 https://github.com/Microsoft/TypeScript/wiki/What's-new-in-TypeScript#mapped-types-on-tuples-and-arrays。让我们看看它的实际效果:
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!
}
这就是缺乏对相关记录的支持让你烦恼的地方。在 TypeScript 3.3 中,添加了对调用的支持函数类型的并集 https://github.com/microsoft/TypeScript/pull/29011,但该支持并没有触及这个问题,即:编译器将func
as a union的功能是完全不相关的与类型arg
。当您调用它时,编译器决定它只能安全地接受类型的参数string & number
, which arg
不是(也不是任何实际值,因为string & number
折叠到never
).
所以如果你走这条路你会发现你需要一个类型断言 https://www.typescriptlang.org/docs/handbook/basic-types.html#type-assertions让编译器平静下来:
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
}
人们可能会认为这是你能做的最好的事情并将其留在那里。
现在,有一种方法可以在 TypeScript 中对存在类型进行编码,但它涉及到Promise
-类似控制反转。不过,在我们走这条路之前,问问自己:你实际上打算做什么do with a MyRecord<T>
当你不知道的时候T
?您可以做的唯一合理的事情是用第二个元素调用它的第一个元素。如果是这样,你可以给出一个更具体的方法这样做不跟踪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
。由于在大多数情况下,这是一小组操作,因此直接支持这些操作通常更容易。
好的,希望有帮助。祝你好运!