无法让嵌套类型保护与打字稿中的联合类型一起使用

Posted

技术标签:

【中文标题】无法让嵌套类型保护与打字稿中的联合类型一起使用【英文标题】:Unable to get nested type guards to work with union types in typescript 【发布时间】:2019-10-19 10:17:10 【问题描述】:

我正在尝试通过删除任何虚假/错误值来清理数据,然后再将其传递给我的消费函数。但我无法让类型保护正常工作。

我遇到以下错误

类型“ExcludeUnusableValues”不可分配给类型“T”。

键入'错误 | prop1:字符串;道具2:字符串; ' 不可分配给类型 ' prop1: string;道具2:字符串; '。

interface BaseVariant 
  id: number
  data: [s:string]:any | Error


interface VariationOne extends BaseVariant 
  type: 'One'
  key1: string
  data:
  | 
      prop1: string
      prop2: string
    
  | Error

interface VariationTwo extends BaseVariant 
  type: 'Two'
  key2: number

interface VariationThree extends BaseVariant 
  type: 'Three'
  key3: number
  data:
  | 
      prop3: string
      prop4: number
    
  | Error


type Variations = VariationOne | VariationTwo | VariationThree

type Omit<T, K> = Pick<T, Exclude<keyof T, K>>

// Removes falsy/error values from object properties
type ExcludeUnusableValues<
  T,
  K extends keyof T,
  C = Error | null
> = Omit<T, K> &  [a in K]-?: Exclude<T[a], C> 

const hasValidData = <T extends Variations>(item: T): item is ExcludeUnusableValues<T, 'data'> => 
  return !(item.data instanceof Error)
 

const foo = (item: Variations) => 
  if (!item || !hasValidData(item)) 
    return null
  

  switch (item.type) 
    case 'One':
      return barOne(item);
    case 'Two':
      return barTwo(item);
    case 'Three':
      return barThree(item);

  


const barOne = (item: ExcludeUnusableValues<VariationOne, 'data'>) => 
  console.log(item.data.prop1)

const barTwo = (item: ExcludeUnusableValues<VariationTwo, 'data'>) => 
  console.log(item.data)

const barThree = (item: ExcludeUnusableValues<VariationThree, 'data'>) => 
  console.log(item.data.prop3)

TypeScript PlayGround Link

【问题讨论】:

【参考方案1】:

我将尝试通过首先清理 sn-p 来回答它,因为发生了很多事情:

// helper types
type Omit<T, K> = Pick<T, Exclude<keyof T, K>>
type Truthy<T, F> =  [K in keyof T]-?: Exclude<T[K], F> 
type PickTruthy<T, K extends keyof T, F = Error | null> = Pick<Truthy<T, F>, K>


// main code
interface VariationOne 
  type: "One"
  data:  foo: string  | Error


interface VariationTwo 
  type: "Two"
  data:  baz: number  | Error


type Variations = VariationOne | VariationTwo


declare function hasValidData<T extends Variations>(item: T): item is PickTruthy<T, "data">

现在我们在hasValidData() (see in playground) 中看到了真正的错误:

类型谓词的类型必须可分配给其参数的类型。

类型 Pick&lt;Truthy&lt;T, Error&gt;, "data"&gt; 不可分配给类型 T

发生这种情况是因为类型谓词仅将一种类型细化(缩小)为另一种类型。如果结果类型比目标类型“宽”,它将不起作用。我们可以在unknown (widest/Top type) 和never (narrowest/Bottom type) (see in playground) 的帮助下确认这一点。

type Foo = number

declare function UnknownisFoo(x: unknown): x is Foo
declare function FooisNever(x: Foo): x is never

declare function NeverisFoo(x: never): x is Foo // ERROR!
declare function FooisUnknown(x: Foo): x is unknown // ERROR!

这意味着在我修改后的你的原始代码版本中:

    类型PickTruthy&lt;T, "data"&gt; 也比Variations 窄:具有更多键的对象更“具体” 类型参数T extends VariationsVariations窄:TVariations的SubType

由于这两种类型之间没有直接比较,因此您无法按照您描述的方式细化目标类型参数。

最好的办法是使用 isError() 谓词保护检查 item.data 本身是否错误

interface VariationOne 
  type: "One"
  data:  foo: string  | Error


interface VariationTwo 
  type: "Two"
  data:  baz: number  | Error


type Variations = VariationOne | VariationTwo


declare const x: Variations
const  data  = x

declare function isError(item: unknown): item is Error

if (isError(data)) 
  console.log('ERROR:', data.message)

【讨论】:

以上是关于无法让嵌套类型保护与打字稿中的联合类型一起使用的主要内容,如果未能解决你的问题,请参考以下文章

我们如何在打字稿中获得嵌套对象类型

如何从打字稿中的标记联合类型中提取类型?

打字稿中具有联合类型键的松散类型对象

字符串枚举类似于打字稿中的类型[重复]

基于嵌套对象内属性的打字稿联合

使用类型推断展平嵌套数组