我想让这两个函数变得通用:
/**
* Unpack an element if it is packed.
* @param data - Data from which to unpack.
*/
export const unpack = (
data: undefined | string | string[],
index: number = 0,
): undefined | string => (Array.isArray(data) ? data[index] : data);
/**
* Pack an element if it is unpacked.
* @param data - Data to pack.
*/
export const pack = (
data: undefined | string | string[],
): undefined | string[] =>
Array.isArray(data) || data === undefined ? data : [data];
这是我的尝试:
/**
* Unpack an element if it is packed.
* @param data - Data from which to unpack.
*/
export const unpack = <T,>(
data: T,
index: number = 0,
): T extends unknown[] ? T[number] : T =>
Array.isArray(data) ? data[index] : data;
/**
* Pack an element if it is unpacked.
* @param data - Data to pack.
*/
export const pack = <T,>(data: T): T extends unknown[] | undefined ? T : T[] =>
Array.isArray(data) || data === undefined ? data : [data];
对于通用的
unpack
函数,TypeScript 不会抱怨。然而,对于通用 pack
函数,TypeScript 会生成以下错误:
Type 'T | (T & ({} | null))[]' is not assignable to type 'T extends unknown[] | undefined ? T : T[]'.
Type 'T' is not assignable to type 'T extends unknown[] | undefined ? T : T[]'.
通用
unpack
函数的输入是否正确?如何修复通用 pack
函数的输入?
TypeScript 目前(从 TS5.4 开始)无法使用 控制流分析 直接影响 generic 类型参数。里面
const pack = <T,>(param: T): T extends unknown[] | undefined ? T : T[] =>
Array.isArray(param) || param === undefined ? param : [param];
类型检查器也许可以理解,在条件表达式的 true 分支中,
param
的类型为 T & (any[] | undefined)
。但类型参数 T
不受影响。因此,在这种情况下返回 param
将无法进行类型检查,因为您将 T & (any[] | undefined)
分配给 T extends unknown[] | undefined ? T : T[]
。您想说检查 param
直接暗示 T
。但到目前为止,还没有实现这样的方法,而且正确执行它至少有些棘手(例如,如果您有一个类型为 t
的值 T extends string
并且您检查了 t === "a"
,这并不意味着 T
是 "a"
。T
可以是 "a" | "b" | "c"
,甚至只是 string
。您所知道的是 "a"
可以分配给 T
,反之亦然)。
因此,以编译器可以准确验证其安全性的方式实现返回条件类型的泛型函数几乎是不可能的。
当前开放的改进功能请求是 microsoft/TypeScript#33014 和 microsoft/TypeScript#33912。在不久的将来,这里可能会发生变化,因为它在 microsoft/TypeScript#57475 的 TypeScript 5.5 迭代计划中提到过。但目前它还不是语言的一部分。
所有这一切意味着,如果您想实现返回条件类型的泛型函数,您可能需要在实现中使用类型安全宽松功能,例如 类型断言 或
any
类型或两者兼而有之:
const pack = <T,>(param: T): T extends unknown[] | undefined ? T : T[] =>
Array.isArray(param) || param === undefined ? param : [param] as any;
请注意,您的
unpack
函数并不明确需要这个,因为 any
潜入其中。在 Array.isArray(data) ? data[index] : data
的真实分支中,data
的类型变为 T & any[]
,因此 data[index]
变为 T[number] & any
,而 any
将整个事物变成 any
。也许 Array.isArray()
应该缩小为 unknown[]
而不是 any[]
,但这会破坏很多人的代码(请参阅 microsoft/TypeScript#43865),所以事情就是这样。
因此,
unpack()
编译时并不没有错误,因为 TypeScript 实际上验证了它是安全的。就是实现被any
“感染”了。事实上,你可能会犯一个明显的错误,而编译器不会注意到:
const unpack = <T,>(
data: T,
index: number = 0,
): T extends unknown[] ? T[number] : T =>
Array.isArray(data) ? data[index] : "WHAAAAA"; // <-- 🤔
所有这些都是说,每当您有通用条件类型时,您都需要格外小心以安全地实现事物,必要时使用类型断言或
any
来消除编译器的警告,也许将来会有更好的东西会一起来的。