【问题标题】:How to make a generic type argument required in typescript?如何在打字稿中制作泛型类型参数?
【发布时间】:2018-11-01 21:50:06
【问题描述】:

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

到目前为止,我发现这样做的唯一方法是使用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,我怎样才能使 arg 成为必需,而不是使用 never,以及在调用 sendWorkRequest 时发生错误?

【问题讨论】:

    标签: typescript generics


    【解决方案1】:

    this open suggestion。我所知道的最好的方法是让T 默认为never(假设never 不是T 的有效类型参数)并定义函数参数之一的类型(1)如果T被指定为非never,那么参数就是你真正想要的类型,(2)如果T被允许默认为never,那么参数有一些虚会产生错误的类型,因为它与参数类型不匹配。

    棘手的部分是,如果调用者将T 设置为它自己的某个范围内类型变量U,我们希望允许调用,即使TypeScript 不能排除U 可能是never .为了处理这种情况,我们使用了一个辅助类型IfDefinitelyNever,它滥用索引访问类型的简化行为来区分一个明确的never 和一个类型变量。需要特殊的G ("gate") 参数来防止来自IfDefinitelyNever 的调用在函数本身的签名中过早地评估其错误分支。

    type RequestType =
      | 'foo'
      | 'bar'
      | 'baz'
    
    interface SomeRequest {
      id: string
      type: RequestType
      sessionId: string
      bucket: string
      params: Array<any>
    }
    
    type ResponseResult = string | number | boolean
    
    const ERROR_INTERFACE_DUMMY = Symbol();
    interface Type_parameter_T_is_required {
      [ERROR_INTERFACE_DUMMY]: never;
    }
    interface Do_not_mess_with_this_type_parameter {
      [ERROR_INTERFACE_DUMMY]: never;
    }
    type IfDefinitelyNever<X, A, B, G extends Do_not_mess_with_this_type_parameter> =
      ("good" | G) extends {[P in keyof X]: "good"}[keyof X] ? B : ([X] extends [never] ? A : B);
    
    async function sendWorkRequest<T extends ResponseResult = never,
      G extends Do_not_mess_with_this_type_parameter = never>(
      type: RequestType & IfDefinitelyNever<T, Type_parameter_T_is_required, unknown, G>,
      ...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() {
      // Error: Argument of type '"foo"' is not assignable to parameter of type
      // '("foo" & Type_parameter_T_is_required) |
      // ("bar" & Type_parameter_T_is_required) |
      // ("baz" & Type_parameter_T_is_required)'.
      const result = await sendWorkRequest('foo')
      result.split('')
    }
    
    test1()
    
    // WORKS
    async function test2() {
      const result = await sendWorkRequest<string>('foo')
      result.split('')
    }
    
    test2()
    
    // ALSO WORKS
    async function test3<U extends ResponseResult>() {
      const result = await sendWorkRequest<U>('foo')
    }
    
    test3()
    

    【讨论】:

    • 啊,有趣的是,你通过类型名称暴露了一条消息,它发生在预期的地方!
    【解决方案2】:

    有一种更简单的方法可以实现上述目标,其中:

    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 的使用非常好,因为除非显式参数化,否则它不会被推断为其他任何东西。

    【讨论】:

    • 简而言之,function foo&lt;T=never&gt;(bar: T) 不起作用,因为当未指定 T 时,TypeScript 将从 bar 的类型推断 T。虽然 function foo&lt;T=never, U extends T=T&gt;(bar: U) 有效,因为 T 无法自动推断,并且默认为 never,并且按照我们的预期失败。
    猜你喜欢
    • 2018-05-02
    • 1970-01-01
    • 1970-01-01
    • 2022-11-11
    • 2021-06-17
    • 2020-12-22
    • 1970-01-01
    • 2021-11-26
    • 2021-04-10
    相关资源
    最近更新 更多