如何制作打字稿中必需的通用类型参数?

时间:2018-11-01 21:50:07

标签: typescript generics

如何使通用模板类型参数成为必需?

到目前为止,我发现做到这一点的唯一方法是使用never,但这会导致在泛型的调用站点以外的其他位置发生错误。

粘贴在此处的TypeScript Playground example

type RequestType =
  | 'foo'
  | 'bar'
  | 'baz'

interface SomeRequest {
  id: string
  type: RequestType
  sessionId: string
  bucket: string
  params: Array<any>
}

type ResponseResult = string | number | boolean

async function sendWorkRequest<T extends ResponseResult = never>(
  type: RequestType,
  ...params
): Promise<T> {
  await this.readyDeferred.promise

  const request: SomeRequest = {
    id: 'abc',
    bucket: 'bucket',
    type,
    sessionId: 'some session id',
    params: [1,'two',3],
  }
  const p = new Promise<T>(() => {})

  this.requests[request.id] = p
  this.worker.postMessage(request)
  return p
}

// DOESN'T WORK
async function test1() {
  const result = await sendWorkRequest('foo')
  result.split('')
}

test1()

// WORKS
async function test2() {
  const result = await sendWorkRequest<string>('foo')
  result.split('')
}

test2()

正如在对test1()的调用中所看到的,由于result.split('')没有never方法,错误发生在.split()

test2中,当我提供通用参数时,效果很好。

如何使arg为必需参数,而不使用从不使用,如果没有给出通用arg,则在调用sendWorkRequest时发生错误?

2 个答案:

答案 0 :(得分:3)

请参见this open suggestion。我所知道的最好方法是像您一样将T默认设置为never(假设never对于T不是有效的类型参数)并定义函数的参数之一,以便(1)如果将T指定为非never,则参数具有您实际想要的类型,而(2)如果T为允许默认设置为never,则该参数具有某种虚拟类型,因为它与参数类型不匹配,将产生错误。

棘手的部分是,如果调用者将T设置为自己的某个作用域内类型变量U,即使TypeScript无法排除{{1} }可以为U。为了处理这种情况,我们使用了辅助类型never,该辅助类型滥用索引访问类型的简化行为来将确定的IfDefinitelyNever与类型变量区分开。需要使用特殊的never(“门”)参数来防止来自函数G的调用在函数本身的签名中过早评估为其假分支。

IfDefinitelyNever

答案 1 :(得分:1)

有一种更简单的方法可以达到上述目的,其中:

  1. 必须提供显式类型参数以无错误地传递参数,并且
  2. 必须提供第二个显式类型参数以获取不是unknown的值
async function sendWorkRequest<ReqT = never, ResT = unknown, InferredReqT extends ReqT = ReqT>(
   request: InferredReqT,
): Promise<ResT> {
  return {} as ResT;
}

// Call does not succeed without an explicit request parameter.
async function test1() {
  const result = await sendWorkRequest('foo');
  //                                   ~~~~~
  // ERROR: Argument of type '"foo"' is not assignable to parameter of type 'never'
}

// Call succeeds, but response is 'unknown'.
async function test2() {
  const result: number = await sendWorkRequest<string>('foo');
  //    ~~~~~~
  // ERROR: Type 'unknown' is not assignable to type 'number'.
  result.valueOf();
}

// Call succeeds and returns expected response.
async function test3() {
  const result = await sendWorkRequest<string, number>('foo');
  result.valueOf();
}

请参见this TypeScript playground

这是通过让TypeScript仅推断最后一个类型参数,而将never设置为非推断的主类型参数的默认值来实现的。如果未传递显式类型参数,则会发生错误,因为传递的值不可分配给默认的never。至于返回类型,它是unknown的很好的用法,因为除非明确地进行参数化,否则它不会被推断为其他任何类型。