如何在打字稿中制作所需的泛型类型参数?

问题描述 投票:0回答:5

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

到目前为止,我发现做到这一点的唯一方法是使用

never
但它会导致错误发生在除泛型调用点之外的不同位置。

粘贴在这里的 TypeScript Playground 示例

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 成为必需的,而不是使用 never,并且如果未给出通用 arg,则在调用

sendWorkRequest
时会发生错误?

typescript generics
5个回答
13
投票

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

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

请参阅 这个 TypeScript 游乐场

这是通过让 TypeScript 仅推断最后一个类型参数,同时将

never
设置为非推断主要类型参数的默认值来实现的。如果未传入显式类型参数,则会发生错误,因为传入的值无法分配给默认值
never
。至于返回类型,这是
unknown
的一个很好的用途,因为除非显式参数化,否则它不会被推断为其他任何内容。


10
投票

请参阅此公开建议。我所知道的最好的方法是让

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
投票

这并不完美,但它提供了改进的开发人员体验。

function myFunction<T = 'A type parameter is required.', AT extends T = T>(arg: AT) : any


1
投票

这就是我如何使我的第一个通用成为强制性的

const fn = <T = never, AT extends T = T>(arg: AT): any => {}

0
投票

使用

const fn = <T = never, AT extends T = T>(arg: AT): any => {}

正如其他答案所建议的,会导致一些问题。主要是因为 never 类型是每种类型的子类型,并且可以分配给每种类型;但是,没有任何类型是 never 的子类型或可分配给 never(never 本身除外)。

因此,如果您的函数返回此

never
,如果您将其分配给其他类型,Typescript 不会引发错误。

我更喜欢使用从唯一符号派生的类型,因为这应该可以防止此分配错误:


declare const RequiredGenericArgument: unique symbol;

export const extractJson = <
  A = typeof RequiredGenericArgument,
  T extends z.Schema = z.Schema
>(
  _str: string,
  schema: T
): (A | undefined)[] => {
© www.soinside.com 2019 - 2024. All rights reserved.