【问题标题】:typescript: how to return proper type from generic function constrained by lookup type generic returning union打字稿:如何从受查找类型泛型返回联合约束的泛型函数返回正确的类型
【发布时间】:2020-08-20 09:09:15
【问题描述】:

假设我们有以下函数实现:

type Action = 'GREET' |'ASK'

function getUnion<T extends Action>(action: T) {
  switch (action) {
  case 'GREET':
    return {hello: 'Guten Tag!'} as const
  case 'ASK':
    return {time: 'Wie spat is es?'} as const
  default:
    return 'WUT?'
  }
}

这个函数的返回类型是下面的联合:

{"WUT?" | { hello: 'Guten Tag!'; time?: undefined; } | { time: 'Wie spat is es?'; hello?: undefined; }}

所以我们可能会想,如果我们通过 switch-case 内部使用的“判别联合”类型的泛型使用函数约束,它将返回特定的分支类型,如下所示:

// ???? NOPE !
// $ExpectType  {time: 'Wie spat is es?'}
const t1 = getUnion('ASK')

不幸的是,这是一个不正确的假设,因为我们得到的是整个联合而不是一个狭窄的类型

// ✅$ExpectType {"WUT?" | { hello: 'Guten Tag!'; time?: undefined; } | { time: 'Wie spat is es?'; hello?: undefined; }}
const t1 = getUnion('ASK')

这是正确的行为还是编译器限制?

无论如何,如何解决?

所以const t1 = getUnion('ASK') 将返回{time: 'Wie spat is es?'}

【问题讨论】:

    标签: typescript generics


    【解决方案1】:

    所以我想出的是:

    此实现缓解了之前的问题,因为它通过条件类型映射器通过使用的函数参数从返回联合中正确返回了缩小的类型:

    type ProperReturn<T> = T extends 'GREET' ? {hello:'Guten Tag!'} : T extends 'ASK' ? {time:'Wie spat is es?'} : 'WUT'
    
    function getUnionStrict<T extends Action>(action: T): ProperReturn<T> {
      switch (action) {
      case 'GREET':
        // ?it needs to be explicitly casted, which is OK I guess?
        return {hello: 'Guten Tag!' } as ProperReturn<T>
      case 'ASK':
        // ? Cast needed
        return {time:'Wie spat is es?'} as ProperReturn<T>
      default:
        // ? Cast needed
        return 'WUT?' as ProperReturn<T>
      }
    }
    
    // ✅ exactly what we wanted 
    // $ExpectType {hello:'Guten Tag!'}
    const t11 = getUnionStrict('ASK')
    
    // ✅
    // $ExpectType {time:'Wie spat is es?'}
    const t22 = getUnionStrict('GREET')
    

    【讨论】:

    • "WUT?" 是无法返回的,对吧?我可能会创建一个类似interface ProperReturn {GREET: {hello: string}; ASK: {time: string}} 的类型,并让返回类型为ProperReturn[T],并跳过条件类型和不可能的"WUT"? 返回。但本质上你是对的,你需要自己定义返回类型条件,然后进行断言(或单调用签名重载,这通常更容易)
    【解决方案2】:

    一种选择是使用映射接口而不是使用条件类型,使返回类型更易于遵循。此外,我通常使用带有泛型的单独实现签名和非泛型并返回联合的实现签名。虽然这不是 100% 类型安全的,但它比类型断言版本更好。

    type Action = 'GREET' | 'ASK'
    interface ProperReturn {
      'GREET': { hello: 'Guten Tag!' }
      'ASK': { time: 'Wie spat is es?' }
    }
    function getUnion<T extends Action>(action: T): ProperReturn[T]
    function getUnion(action: Action): ProperReturn[keyof ProperReturn] {
      switch (action) {
        case 'GREET':
          return { hello: 'Guten Tag!' } as const
        case 'ASK':
          return { time: 'Wie spat is es?' } as const
        default:
          throw "WUT";
      }
    }
    

    Playground Link

    【讨论】:

    • ?是的,比我建议的解决方案更干净。将此标记为解决方案。干杯伙计们!
    【解决方案3】:

    对于像我这样的搜索者:我想出了两个答案的混合体,我认为这既是类型安全的又是干净的:

    type ProperReturn = {
      'GREET': { hello: 'Guten Tag!' }
      'ASK': { time: 'Wie spat is es?' }
    }
    
    // better than retyping 'GREET' | 'ASK'
    type Action = keyof ProperReturn
    
    function getUnion<T extends Action>(action: T): ProperReturn[T] {
      switch (action) {
      case 'GREET':
        return {hello: 'Guten Tag!' } as ProperReturn[T]
      case 'ASK':
        return {time:'Wie spat is es?'} as ProperReturn[T]
      default:
        throw "WUT?"
      }
    }
    
    // t1: { hello: 'Guten Tag!'; }
    const t1 = getUnion('GREET')
    
    // t2: { time: 'Wie spat is es?'; }
    const t2 = getUnion('ASK')
    

    Playground link

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-06-29
      • 2019-09-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-11-23
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多