【问题标题】:Is it impossible to have a function return a user-defined type guard?让函数返回用户定义的类型保护是不可能的吗?
【发布时间】:2022-01-07 08:45:17
【问题描述】:

考虑以下类型和子类型:

interface Animal {
  type: string;
}

interface Dog extends Animal {
  type: 'dog';
  sound: 'bark';
}

interface Cat extends Animal {
  type: 'cat';
  purpose: 'world_domination';
}

还有一种方法可以从 type 的值中获取 Animal 子类型:

type Animals = Dog | Cat;
type AnimalFromType<T extends Animals['type']> = Extract<Animals, { type: T }>;

现在假设我有一个动物数组,我想创建 一个返回用户定义类型保护的函数,然后可以在 Array.filter() 调用中使用它:

function createFilter<T extends 'dog' | 'cat'>(type: T): (x: any) => x is AnimalFromType<T> {
  return x => x.type === type;
}

问题是函数似乎不可能返回类型保护函数 - 编译器向 TS2322 抱怨 (x: any) =&gt; boolean 不能分配给 (x: any) =&gt; x is AnimalFromType&lt;T&gt; - 签名必须是类型谓词。出于某种原因,编译器不理解从 lambda 返回的布尔表达式与外部函数签名的 x is AnimalFromType&lt;T&gt; 部分匹配。

这是为什么呢?什么会阻止编译器理解 createFilter('dog') 返回一个用户定义的类型保护(它接受一个对象并返回 x is Dog)?

编辑:@jcalz 在此处提供的完整 MRE:https://tsplay.dev/wX219m

【问题讨论】:

  • 请提供minimal reproducible example,清楚地表明您面临的问题。理想情况下,有人可以将代码放入像The TypeScript Playground (link here!) 这样的独立IDE 中,然后立即着手解决问题,而无需首先重新创建它。所以不应该有拼写错误、不相关的错误或未声明的类型或值。 (可能应该删除该问题的任何“超出范围”的内容)
  • 答案将是编译器仅在将布尔返回函数的返回值注释为类型谓词的特定情况下将boolean 推断为可分配给类型谓词。所以你必须添加一个像(x): x is AnimalFromType&lt;T&gt; =&gt; x.type === type这样的注解来编译它,因为那是boolean-returning函数,而不是createFilter()(一个boolean-returning-function-returning函数,这是不一样的事物)。我很乐意写一个答案,但如果可能的话,我想先把minimal reproducible exampleanimalsAnimalFromType 联系起来。
  • 在解释为什么其他答案似乎没有解决这个问题时,我最终在this code here 中写了我希望是我自己的minimal reproducible example。如果该方法满足您的需求,您能否编辑问题以包含设置,我将发布答案,解释为什么它适用于我的版本但不适用于您的版本?
  • 谢谢@jcalz。您的 MRE 有效,我将更新问题以等待您的回答。我不能说我理解 为什么 编译器无法理解从返回的 lambda 函数返回的布尔表达式表示外部函数的类型保护签名,但我想这只是一个限制TS?出于好奇,我可能会对 RC 进行 ping 操作。 :)

标签: typescript typeguards


【解决方案1】:

权威答案请见microsoft/TypeScript#14826

如果您希望编译器看到 boolean 值可分配给 type predicate,则需要将其用作函数的返回值,该函数的 return type is explicitly annotated 是该类型谓词。这是类型检查器唯一支持的方法。

用户定义的类型保护及其类型谓词不会在 TypeScript 中传播,因此您对“类型谓词的传递性”之类的任何直觉可能不会成功。所以你会得到这种行为:

function isString(x: any): x is string {
  return typeof x === "string"; // okay
}

function isStringMaker(): (x: any) => x is string {
  return (x: any) => typeof x === "string"; // error
}

因为函数(x: any) =&gt; typeof x === string 没有类型谓词类型的带注释的返回值。编译器只是为它推断(x: any) =&gt; boolean,然后一切都中断了。没有contextual typing 说“因为isStringMaker() 想要返回一个返回类型谓词的函数,我们应该尝试将typeof x === string 解释为这样一个类型谓词,因为它在相关位置。 "也许可以实现这样的事情,但事实并非如此,而且似乎这是设计要求围绕类型谓词进行非常本地和明确的意图表达。

要使用isStringMaker,我们可以注释返回的箭头函数的返回类型:

function isStringMaker(): (x: any) => x is string {
  return (x: any): x is string => typeof x === "string"; // okay
  // ----------> ^^^^^^^^^^^^^ <--- return type annotation
}

我可以在手册中找到任何明显的文档都没有记录,您可以通过这种方式注释箭头函数的返回类型,但在上面链接的 ms/TS#14826 中提到了它,因此您至少可以查阅它。无论如何,一旦你这样做了,你就不需要再注释 isStringMaker() 返回类型了,因为 将被推断出来......所以如果你关心冗余,你可以在那里消除它:

function isStringMaker() {
  return (x: any): x is string => typeof x === "string"; 
}

因此,我的建议是更改您的createFilter(),以便实际的类型保护函数具有返回类型注释:

function createFilter<T extends 'dog' | 'cat'>(type: T) {
  return (x: any): x is AnimalFromType<T> => x.type === type;
}

Playground link to code

【讨论】:

  • 出色的答案。也欣赏到 TS 问题的链接。非常感谢。
【解决方案2】:

也许一个简单的 instanceof 可以解决您的问题。您可以在数组过滤器中使用它:

animals.filter(animal => animal instanceof Dog )

我使用类来定义类型。

interface Animal {
  type: string;
  name: string;
}

class Cat implements Animal {
  type: string;
  name: string;
  purpose: string;

  constructor(name: string) {
    this.type = 'cat';
    this.name = name;
    this.purpose = 'world_domination';
  }
}

class Dog implements Animal {
  type: string;
  name: string;

  constructor(name: string) {
    this.type = 'dog';
    this.name = name;
    this.sound = 'bark';
  }
}

const animals = [ new Dog('dog0'), new Cat('cat0'), new Dog('dog1'), new Cat('cat1')]

console.log(animals.filter(animal => animal instanceof Dog ))

【讨论】:

  • 这似乎没有回答所问的问题;你能解释一下它是如何解决这个问题的吗?
  • JHH 想要根据类型从数组中过滤数据。我的代码解决了这个问题,因为它只会返回 array.filter() 函数中给定的特定类型的对象。在他的例子中有描述。
  • 当我读到它时,OP 想要“创建一个返回 user-defined type guard 的函数”,其中可以在 filter() 中使用这样一个用户定义的类型保护来缩小返回数组中的元素。例如,this。您所做的与用户定义的类型保护无关;您在这里根本没有进行类型保护,至少在编译器知道过滤后的数组仅包含 Dog 元素的范围内。
  • 正如@jcalz 所说,这并不能解决问题。我完全了解如何缩小类型,或者如何创建用户定义的类型保护。问题特别是关于为什么创建一个返回用户定义的类型保护函数的函数是有问题的。但是感谢您的努力!
  • 感谢您的反馈,我真的只是想提供帮助。我现在可以看到不同之处,我什至通过 jcalz 代码学到了更多东西。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-01-30
  • 1970-01-01
  • 2019-03-02
  • 2015-12-19
  • 2021-10-31
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多