在 Typescript 中,我如何将 Array<T> 转换为 Map<K, V> 并在 T 是元组 [K, V] 时推断 K 和 V 而如果不是则具有编译时保护
In Typescript, how can I convert an Array<T> to a Map<K, V> and infer K and V if T is a tuple [K, V] while having compile time protection if it isn't
标题中的问题几乎说明了一切。问题是 T
不能被限制。
这是我尝试过的:
class ArrayWrapper<T> {
constructor(private arr: T[]) {}
toMap<K, V>() {
return new Map(<T extends [K, V] ? [K, V][] : never>this.arr);
}
}
我也试过这个:
class ArrayWrapper<T> {
constructor(private arr: T[]) {}
toMap() {
return new Map(<T extends [infer K, infer V] ? [K, V][] : never>this.arr);
}
}
在上述两种情况下,编译器都会给我一个“将类型 'T[]' 转换为 ... 可能是一个错误,因为两种类型都没有与另一种充分重叠。如果这是故意的,请将表达到'unknown'第一个”错误。
所以我尝试了一些不同的东西,而不是从不,我将条件类型的错误分支更改为 never[]
。这消除了编译错误,但现在当我做类似的事情时:
class ArrayWrapper<T> {
constructor(private arr: T[]) {}
toMap() {
return new Map(<T extends [infer K, infer V] ? [K, V][] : never[]>this.arr);
}
}
const notConvertibleToMap = [1, 2, 3];
const convertibleToMap: [number, string][] = [[1, 'one'], [2, 'two'], [3, 'three']];
const arr1 = new ArrayWrapper(notConvertibleToMap);
const map1 = arr1.toMap();
const arr2 = new ArrayWrapper(convertibleToMap);
const map2 = arr2.toMap();
编译器为 map1
和 map2
推断 Map。
我希望编译器能够:
- 在
map1
的情况下,当 T
不是 [K,V]
时尝试调用 toMap()
要么给出错误,要么至少可以推断出一个 return 类型的 never
因为此函数调用将抛出错误,因为 T
不是元组并且 Map 构造函数将抛出。
- 在
map2
的情况下,其中 T
是 [K, V]
,我希望编译器为 map2
正确推断出 Map<K, V>
的类型.
就像我一开始说的,显然是这样限制class中T
的类型
class ArrayWrapper<T extends [K, V], K, V> {
constructor(private arr: T[]) {}
toMap() {
return new Map(this.arr);
}
}
将允许 K
和 V
的正确推断,但我不想限制 T。我需要这个 class 来接受任何类型的包含值的数组T
.
对此进行了额外的尝试:
class ArrayWrapper<T> {
constructor(private arr: T[]) {}
toMap() {
if (this.isArrayOfTuple(this.arr)) {
return new Map(this.arr);
}
throw new Error("called toMap() on ArrayWrapper that is not an array of tuples");
}
isArrayOfTuple<K, V>(arr: T[]): arr is [K, V][] {
return arr[0] instanceof Array && arr[0].length === 2;
}
}
但不幸的是,编译器抱怨 isArrayOfTuple
的 return 类型,说
A type predicate's type must be assignable to its parameter's type.
Type '[K, V][]' is not assignable to type 'T[]'.
Type '[K, V]' is not assignable to type 'T'.
'T' could be instantiated with an arbitrary type which could be unrelated to '[K, V]'
如果对于某些 K
和 V
,如果 T
不能分配给 [K, V]
,您希望编译器使调用 toMap()
出错,那么在某种意义上,在这种情况下输出类型是什么并不重要。它可以是 Map<unknown, unknown>
或 Map<never, never>
或任何东西,只要 toMap()
调用是编译器错误。我想你最终会遇到运行时错误(如果你真的关心的话,你可以 wade through the spec)所以函数不会 return...“实际”return 类型是 never
可以安全地扩展为 Map<unknown, unknown>
或任何你想要的,而不会导致类型安全问题。
无论如何,要使编译器发生错误,您可以给 toMap()
一个 this
parameter which requires this
be of ArrayWrapper<[any, any]>
or something equivalent. You could use conditional type inference 以从 T
手动推断 K
和 V
:
toMap(this: ArrayWrapper<[any, any]>) {
type K = T extends [infer K, any] ? K : never;
type V = T extends [any, infer V] ? V : never;
return new Map<K, V>(this.arr);
}
但将 toMap()
设为 generic method 并自动推断 K
和 V
更容易:
toMap<K, V>(this: ArrayWrapper<[K, V]>) {
return new Map<K, V>(this.arr);
}
任何一种方法都会为您提供您正在寻找的行为:
const arr1 = new ArrayWrapper(notConvertibleToMap);
const map1 = arr1.toMap(); // <-- compiler error
// --------> ~~~~
// The 'this' context of type 'ArrayWrapper<number>' is not assignable to method's
// 'this' of type 'ArrayWrapper<[unknown, unknown]>'.
const arr2 = new ArrayWrapper(convertibleToMap);
const map2 = arr2.toMap(); // okay
// const map2: Map<number, string>
标题中的问题几乎说明了一切。问题是 T
不能被限制。
这是我尝试过的:
class ArrayWrapper<T> {
constructor(private arr: T[]) {}
toMap<K, V>() {
return new Map(<T extends [K, V] ? [K, V][] : never>this.arr);
}
}
我也试过这个:
class ArrayWrapper<T> {
constructor(private arr: T[]) {}
toMap() {
return new Map(<T extends [infer K, infer V] ? [K, V][] : never>this.arr);
}
}
在上述两种情况下,编译器都会给我一个“将类型 'T[]' 转换为 ... 可能是一个错误,因为两种类型都没有与另一种充分重叠。如果这是故意的,请将表达到'unknown'第一个”错误。
所以我尝试了一些不同的东西,而不是从不,我将条件类型的错误分支更改为 never[]
。这消除了编译错误,但现在当我做类似的事情时:
class ArrayWrapper<T> {
constructor(private arr: T[]) {}
toMap() {
return new Map(<T extends [infer K, infer V] ? [K, V][] : never[]>this.arr);
}
}
const notConvertibleToMap = [1, 2, 3];
const convertibleToMap: [number, string][] = [[1, 'one'], [2, 'two'], [3, 'three']];
const arr1 = new ArrayWrapper(notConvertibleToMap);
const map1 = arr1.toMap();
const arr2 = new ArrayWrapper(convertibleToMap);
const map2 = arr2.toMap();
编译器为 map1
和 map2
推断 Map
我希望编译器能够:
- 在
map1
的情况下,当T
不是[K,V]
时尝试调用toMap()
要么给出错误,要么至少可以推断出一个 return 类型的never
因为此函数调用将抛出错误,因为T
不是元组并且 Map 构造函数将抛出。 - 在
map2
的情况下,其中T
是[K, V]
,我希望编译器为map2
正确推断出Map<K, V>
的类型.
就像我一开始说的,显然是这样限制class中T
的类型
class ArrayWrapper<T extends [K, V], K, V> {
constructor(private arr: T[]) {}
toMap() {
return new Map(this.arr);
}
}
将允许 K
和 V
的正确推断,但我不想限制 T。我需要这个 class 来接受任何类型的包含值的数组T
.
对此进行了额外的尝试:
class ArrayWrapper<T> {
constructor(private arr: T[]) {}
toMap() {
if (this.isArrayOfTuple(this.arr)) {
return new Map(this.arr);
}
throw new Error("called toMap() on ArrayWrapper that is not an array of tuples");
}
isArrayOfTuple<K, V>(arr: T[]): arr is [K, V][] {
return arr[0] instanceof Array && arr[0].length === 2;
}
}
但不幸的是,编译器抱怨 isArrayOfTuple
的 return 类型,说
A type predicate's type must be assignable to its parameter's type.
Type '[K, V][]' is not assignable to type 'T[]'.
Type '[K, V]' is not assignable to type 'T'.
'T' could be instantiated with an arbitrary type which could be unrelated to '[K, V]'
如果对于某些 K
和 V
,如果 T
不能分配给 [K, V]
,您希望编译器使调用 toMap()
出错,那么在某种意义上,在这种情况下输出类型是什么并不重要。它可以是 Map<unknown, unknown>
或 Map<never, never>
或任何东西,只要 toMap()
调用是编译器错误。我想你最终会遇到运行时错误(如果你真的关心的话,你可以 wade through the spec)所以函数不会 return...“实际”return 类型是 never
可以安全地扩展为 Map<unknown, unknown>
或任何你想要的,而不会导致类型安全问题。
无论如何,要使编译器发生错误,您可以给 toMap()
一个 this
parameter which requires this
be of ArrayWrapper<[any, any]>
or something equivalent. You could use conditional type inference 以从 T
手动推断 K
和 V
:
toMap(this: ArrayWrapper<[any, any]>) {
type K = T extends [infer K, any] ? K : never;
type V = T extends [any, infer V] ? V : never;
return new Map<K, V>(this.arr);
}
但将 toMap()
设为 generic method 并自动推断 K
和 V
更容易:
toMap<K, V>(this: ArrayWrapper<[K, V]>) {
return new Map<K, V>(this.arr);
}
任何一种方法都会为您提供您正在寻找的行为:
const arr1 = new ArrayWrapper(notConvertibleToMap);
const map1 = arr1.toMap(); // <-- compiler error
// --------> ~~~~
// The 'this' context of type 'ArrayWrapper<number>' is not assignable to method's
// 'this' of type 'ArrayWrapper<[unknown, unknown]>'.
const arr2 = new ArrayWrapper(convertibleToMap);
const map2 = arr2.toMap(); // okay
// const map2: Map<number, string>