【问题标题】:Get rid of type assertion when checking if value is part of union在检查值是否是联合的一部分时摆脱类型断言
【发布时间】:2022-01-18 07:46:58
【问题描述】:

我有一个联合类型,它部分基于一个数组——我还想用它来检查运行时的值。但是,TypeScript 强制我在这里使用类型断言。 考虑下面的代码:

const Pets = ["dog", "cat"] as const
type Pet = typeof Pets[number]

type Animal = Pet | "tiger"

function checkDanger(animal: Animal) {
  if (Pets.includes(animal as Pet)) {
        return "not dangerous"
    }
    return "very dangerous"
}

问题出在as Pet 部分。

如果我忽略它,我会得到:Argument of type 'Animal' is not assignable to parameter of type '"dog" | "cat"'. Type '"tiger"' is not assignable to type '"dog" | "cat"'.

但是,在更复杂的实际情况中,该断言可能会产生我想避免的影响。有没有办法在没有断言的情况下做这样的事情?

【问题讨论】:

    标签: typescript static-typing


    【解决方案1】:

    这是includes 的一个已知问题,请参阅issues/26255

    但是,有一个解决方法。您可以创建自定义curriedtypeguard

    const inTuple = <Tuple extends string[]>(
        tuple: readonly [...Tuple]) => (elem: string
        ): elem is Tuple[number] =>
            tuple.includes(elem)
    
    // (elem: string) => elem is "dog" | "cat"
    const inPets = inTuple(Pets)
    

    让我们试试吧:

    const Pets = ["dog", "cat"] as const
    type Pet = typeof Pets[number]
    
    type Animal = Pet | "tiger"
    
    const inTuple = <Tuple extends string[]>(
        tuple: readonly [...Tuple]) => (elem: string
        ): elem is Tuple[number] =>
            tuple.includes(elem)
    
    // (elem: string) => elem is "dog" | "cat"
    const inPets = inTuple(Pets)
    
    function checkDanger(animal: Animal) {
        if (inPets(animal)) {
            animal // "dog" | "cat"
            return "not dangerous"
        }
        return "very dangerous"
    }
    

    因为你有一个条件语句,我假设这个函数可以被重载以缩小返回类型:

    const Pets = ["dog", "cat"] as const
    type Pet = typeof Pets[number]
    
    type Animal = Pet | "tiger"
    
    const inTuple = <Tuple extends string[]>(
        tuple: readonly [...Tuple]) => (elem: string
        ): elem is Tuple[number] =>
            tuple.includes(elem)
    
    // (elem: string) => elem is "dog" | "cat"
    const inPets = inTuple(Pets)
    
    
    function checkDanger(animal: Pet): "not dangerous"
    function checkDanger(animal: Animal): "very dangerous"
    function checkDanger(animal: string) {
        if (inPets(animal)) {
            animal // "dog" | "cat"
            return "not dangerous"
        }
        return "very dangerous"
    }
    
    const result = checkDanger('tiger') // very dangerous
    const result2 = checkDanger('cat') // not dangerous
    

    Playground 重载签名的顺序很重要。

    您可能已经注意到,我的代码中没有类型断言。

    inTuple 类型保护有效,因为tuple 被视为函数体内的字符串数组。这意味着允许此操作,因为tuple[number]elem 可以相互分配。

    const inTuple = <Tuple extends string[]>(
        tuple: readonly [...Tuple]) =>
        (elem: string): elem is Tuple[number] => {
            tuple[2] = elem // ok
            elem = tuple[3] // ok
            return tuple.includes(elem)
        }
    

    【讨论】:

      【解决方案2】:

      不幸的是,这是 typescript 编译器的限制。但是,您可以使用 Type Guards,而不是在 animal as Pet 的同一行中编写数千个函数

      function isPet(something: Pet | Animal) : something is Pet {
          return Pets.includes(something as Pet)
      } 
      
      function checkDanger(animal: Animal) {
          if (isPet(animal)) {
              return "not dangerous"
          }
          return "very dangerous"
      }
      

      这种方法更适用,因为如果您要重命名类型定义名称,重构代码会更容易。

      不过,如果你声明一个只读数组,你就会失去灵活性,并且必须执行类型断言。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2021-10-26
        • 2020-02-24
        • 2023-01-24
        • 2020-01-22
        • 1970-01-01
        • 2019-05-26
        • 1970-01-01
        • 2018-01-11
        相关资源
        最近更新 更多