简体   繁体   English

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

[英]typescript: how to return proper type from generic function constrained by lookup type generic returning union

So let's say we have following function implementation:所以假设我们有以下功能实现:

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

Return type of this function is following union:此函数的返回类型如下联合:

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

So what we may think is that if we use the function constraint by our generic by "discriminant union" type used inside the switch-case, it would return that particular branch type, like following:所以我们可能会认为,如果我们通过 switch-case 中使用的“判别联合”类型使用泛型的函数约束,它将返回该特定分支类型,如下所示:

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

Unfortunately that's incorrect assumption as we're getting whole union instead of that one narrowed type不幸的是,这是错误的假设,因为我们得到的是整个联合而不是一种缩小的类型

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

Is this proper behaviour or rather Compiler limitation?这是正确的行为还是编译器限制?

Anyways, how can this be solved?无论如何,这如何解决?

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

So what I came up with is following:所以我想出了以下内容:

This implementation mitigates previous issue, as it properly returns narrowed type from return union by used function argument, via conditional types mapper:此实现减轻了以前的问题,因为它通过条件类型映射器通过使用的函数参数从返回联合正确返回缩小类型:

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

One option is to use a mapping interface instead of using a conditional type, makes the return type easier to follow.一种选择是使用映射接口而不是使用条件类型,使返回类型更易于遵循。 Also I generally use a separate implementation signature with the generics and an implementation signature that is not generic and returns a union.此外,我通常使用带有泛型的单独实现签名和非泛型并返回联合的实现签名。 While this is not 100% type safe it is better than the type assertion version.虽然这不是 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 游乐场链接

For searchers like me: I came up with a mix of the two answers, that I think is both type-safe and cleaner:对于像我这样的搜索者:我想出了两个答案的混合,我认为它们既类型安全又更清洁:

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 游乐场链接

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM