打字稿:如何从查找类型通用返回联合约束的通用函数中返回正确的类型

时间:2019-08-04 19:34:16

标签: typescript generics

所以说我们有以下函数实现:

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; }}

所以我们可能会认为,如果我们在切换用例中使用泛型“区分联合”类型的函数约束,它将返回该特定分支类型,如下所示:

// ? 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?'}吗?

3 个答案:

答案 0 :(得分:2)

所以我想到的是:

此实现减轻了先前的问题,因为它可以通过条件类型映射器通过使用的函数参数从return union正确返回缩小的类型:

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')

答案 1 :(得分:1)

一种选择是使用映射接口,而不是使用条件类型,这使得返回类型更易于理解。同样,我通常使用带有泛型的单独实现签名和非泛型的实现签名并返回联合。尽管这不是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

答案 2 :(得分:0)

对于像我这样的搜索者:我想出了两个答案的组合,我认为它们都是类型安全且更干净的:

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