微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

“结构类型保护”与 `if` 一起使用,但不能用作数组过滤谓词

如何解决“结构类型保护”与 `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>,在这种情况下,TPetS 是 {{1 }}。但是,{ type: "cat" } 不扩展 { type: "cat" },因此该签名不适用,因此属于普通过滤器签名。


它适用于单个元素的情况,因为 TS 的缩小逻辑实际上比 Pet 的签名中表达的要聪明一点 - 它实际上结合了类型保护和原始类型的结果 - 类似于.filterPet & { 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 举报,一经查实,本站将立刻删除。