如何使通用模板类型参数成为必需的?
到目前为止,我发现做到这一点的唯一方法是使用
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
时会发生错误?
有一种更简单的方法可以实现上述目的,其中:
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
的一个很好的用途,因为除非显式参数化,否则它不会被推断为其他任何内容。
请参阅此公开建议。我所知道的最好的方法是让
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()
这就是我如何使我的第一个通用成为强制性的
const fn = <T = never, AT extends T = T>(arg: AT): any => {}
使用
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)[] => {