我正在尝试这样做,以便如果类型 number | number[]
已知它可以传递给通用函数,该函数验证它是一个数组,而不会丢失数组类型是 number[]
的事实。简化示例:
function assertArray(value: unknown): unknown[] {
if (Array.isArray(value)) {
return value;
}
throw new Error();
}
用法:
const value: number | number[] = [1, 2, 3];
const verified = assertArray(value);
在上面的示例中,我希望 assertArray
的返回类型足够智能,能够确定它正在返回 number[]
,因为它传递了 number | number[]
并确保它是唯一的数组选项 number[]
。但是,它当前返回unknown[]
。我想做到这一点,同时保持将任何类型的数组传递给assertArray的灵活性,并让它返回正确类型的数组(如果可以确定)。
最佳答案
当您使用像 narrowing 这样的 type guard function 时发生的那种 Array.isArray()
很难模拟。您想要实现这一目标的程度实际上取决于您的用例。
本质上,当您想要将类型 T
缩小为另一种类型 U
时,编译器必须选择将 T
中的 union 过滤到可分配给 U
的那些成员(即 Extract<T, U>
)还是 0x104 567916 T
与 U
(即 T & U
) ,或这些的某种组合。从概念上讲,这些应该是相似的,但实际上,存在各种可观察到的差异。
在您的情况下,如果 U
是 unknown[]
,那么这就是人们可能期望的,并且会通过各种基本方法发生:
T
number | number[]
number[]
number[]
👍number[] & unknown[]
👎unknown
unknown[]
never
👎unknown[]
👍ArrayLike<string>
string[]
👎never
🤷(ArrayLike<string> & unknown[]
类型仅意味着它是具有数字 ArrayLike<T>
属性和数字 length
的类型,以便您可以在任何数字索引属性中读取和写入 T
类型的值。它是 index signature 。)
很明显,这两种基本方法本身都不能完美地工作。这意味着我们需要一些更复杂的东西,但我们仍然可能无法得到我们想要的东西。
这是一种可能的前进方向:
function assertArray<T extends unknown>(value: T) {
if (Array.isArray(value)) {
return value as
T extends unknown[] ? T : (T & unknown[]);
}
throw new Error();
}
返回类型为 T extends unknown[] ? T : T & unknown[]
,即 array-like ,用于过滤联合体,但它不会删除不匹配的联合体成员,而是将这些成员与 unknown[]
相交。这会导致以下行为:
T
assertArray()
number | number[]
number[]
number[]
👍unknown
unknown[]
unknown[]
👍ArrayLike<string>
string[]
ArrayLike<string> & unknown[]
🤷所以,这非常好,并且涵盖了您问题中提到的用例。
但它并不完美,因为人们可能希望在成功调用 ArrayLike<string>
后将 Array<string>
缩小到 Array.isArray(x)
。并且类数组类型的交集并不总是有用。但这是由 x is unknown[]
形式的类型保护函数产生的,所以也许它已经足够好了!
关于typescript - 如果已知数组类型,则保留数组类型,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/76784609/