如何解决“结构类型保护”与 `if` 一起使用,但不能用作数组过滤谓词
我有一个联合类型(在下面的示例中为 Pet
),它组合了多个对象类型,每个类型都有一个 type
属性来指示它们的类型。有时我有一个联合类型 (Pet[]
) 的数组,需要根据 .filter()
属性 type
它。这本身工作得很好,但为了避免冗余类型声明,我想确保 .filter()
调用的结果自动正确键入。
User-defined type guards 似乎是解决这个问题的完美解决方案,所以我实现了一个只检查 type
属性并将类型缩小到 { type: 'something' }
而无需明确声明的方法完整类型(以下称为 isCatLike
)。我尝试在 if
中使用它,它正确地将我的类型从 Pet
缩小到 Cat
。
然后我尝试将它用作 .filter()
的谓词,这次类型根本没有缩小。结果数组的类型仍然是 Pet[]
,尽管我的 if
实验表明类型保护通常能够从 Pet
缩小到 Cat
。
作为另一个实验,我尝试稍微改变类型保护并使类型谓词更加明确(is Cat
而不是 is { type: 'cat' }
,突然 .filter()
调用 正确地缩小了类型 从 Pet[]
到 Cat[]
(此函数在下面称为 isCat
)。
type Cat = { type: 'cat'; name: string; purrs: boolean }
type Dog = { type: 'dog'; name: string; woofs: boolean }
type Pet = Cat | Dog
declare const pets: Pet[]
const isCatLike = (pet: any): pet is { type: 'cat' } => pet.type === 'cat'
const isCat = (pet: Pet): pet is Cat => pet.type === 'cat'
for (const pet of pets) {
if (isCatLike(pet)) {
pet // Cat -> Correct!
}
if (isCat(pet)) {
pet // Cat
}
}
const catLikes = pets.filter(isCatLike)
catLikes // Pet[] -> Incorrect!
const cats = pets.filter(isCat)
cats // Cat[]
Open the example on the TypeScript Playground 自己检查类型。
现在的问题是我不能使用更明确的方法(由 isCat
函数说明),因为我的实际代码在联合中有更多类型,并且谓词也创建通过函数 (isType(type: string)
)。
所以我现在想知道的是:
为什么我的“结构类型保护”在 if
语句的上下文中工作,而不是作为过滤数组的谓词?在这两种情况下,它的工作方式不应该完全相同吗?是我做错了什么还是我遇到了类型系统的限制?
解决方法
这归结为 Array.filter
的类型是如何编写的:
interface Array<T>
// This is the signature that enables the array to be a different type
filter<S extends T>(predicate: (value: T,index: number,array: T[]) => value is S,thisArg?: any): S[];
// Normal signature that returns the same type of array
filter(predicate: (value: T,array: readonly T[]) => unknown,thisArg?: any): T[];
}
该签名的关键位是 <S extends T>
,在这种情况下,T
是 Pet
,S
将是 {{1 }}。但是,{ type: "cat" }
不扩展 { type: "cat" }
,因此该签名不适用,因此属于普通过滤器签名。
它适用于单个元素的情况,因为 TS 的缩小逻辑实际上比 Pet
的签名中表达的要聪明一点 - 它实际上结合了类型保护和原始类型的结果 - 类似于.filter
与 Pet & { type: "cat" }
相同。
来自手册https://www.typescriptlang.org/docs/handbook/advanced-types.html#user-defined-type-guards
pet is Fish 在这个例子中是我们的类型谓词。谓词采用 parameterName is Type 形式,其中 parameterName 必须是当前函数签名中的参数名称。任何时候使用某个变量调用 isFish 时,TypeScript 都会将该变量缩小到该特定类型如果原始类型兼容。
我猜,由于原始类型是 any
在类型保护中,TS 根本无法缩小范围,如果您将 any
换成 Cat
,您甚至会得到一个编译错误表明 {type: 'cat'}
不可分配给 Cat
原因是缺少其他两个道具。
我认为这是一个提示,并将其他道具设为可选:
type Cat = { type: 'cat'; name?: string; purrs?: boolean }
然后你的谓词开始按照你想要的方式工作。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。