为何泛型查找时无法使用void类型的可选参数?
为什么泛型函数中
void类型参数无法自动省略? 先看几个测试案例:
正常运行的案例1
直接使用void | string作为参数类型,第二个参数可省略:
type OptionalInit = void | string function foo(url: string, init: OptionalInit) { console.log(url) } foo("hello", "world") foo("hello") // 正常运行
正常运行的案例2
通过对象属性查找获取void | string类型,第二个参数同样可省略:
type lookup = { "a": void | string } function foo2(url: string, init: lookup["a"]) { console.log(url) } foo2("hello", "world") foo2("hello") // 正常运行
报错的泛型案例
但换成泛型查找后,即使显式指定泛型参数,省略第二个参数会报错:
type lookup = { "a": void | string } function foo3<T extends keyof lookup>(url: string, init: lookup[T]) { console.log(url) } foo3<"a">("hello", "world") foo3<"a">("hello") // 报错:Expected 2 arguments, but got 1.
报错信息:
test.ts:33:1 - error TS2554: Expected 2 arguments, but got 1. 33 foo3("hello") ~~~~ test.ts:28:52 28 function foo3<T extends keyof lookup>(url: string, init: lookup[T]) { ~~~~~~~~~~~~~~~ An argument for 'init' was not provided.
原因分析
这不是TypeScript的bug,是类型系统的设计逻辑:
- TS只有在参数类型是明确的
void或包含void的非泛型联合类型时,才允许省略该参数。 - 泛型参数的类型是延迟推断的,函数定义阶段TS无法确定
lookup[T]一定会包含void——哪怕你调用时显式指定了T="a",参数的可选性检查是在函数定义时确定的,不会根据调用时的泛型参数动态调整。
实现条件可选参数的解决方案
如果要实现「根据泛型参数决定第二个参数是否可选」的需求,有两种靠谱的方式:
方案1:使用条件类型+剩余参数
通过条件类型判断lookup[T]是否包含void,动态生成参数列表:
type lookup = { "a": void | string, "b": string } // 判断类型是否包含void type ContainsVoid<T> = T extends void ? true : T extends (void | infer _) ? true : false; function foo3<T extends keyof lookup>( url: string, ...args: ContainsVoid<lookup[T]> ? [init?: lookup[T]] : [init: lookup[T]] ) { console.log(url); } // 测试 foo3("a", "world"); // 正常 foo3("a"); // 正常(可选参数) foo3("b", "world"); // 正常(必须传参) foo3("b"); // 报错(符合预期,因为lookup["b"]是string,不含void)
方案2:使用函数重载
针对不同的泛型参数分支定义重载签名,明确参数的可选性:
type lookup = { "a": void | string, "b": string } // 重载签名:针对"a",第二个参数可选 function foo3<T extends "a">(url: string, init?: lookup[T]): void; // 重载签名:针对"b",第二个参数必填 function foo3<T extends "b">(url: string, init: lookup[T]): void; // 实现函数 function foo3<T extends keyof lookup>(url: string, init?: lookup[T]) { console.log(url); } // 测试 foo3("a", "world"); // 正常 foo3("a"); // 正常 foo3("b", "world"); // 正常 foo3("b"); // 报错(符合预期)
内容的提问来源于stack exchange,提问作者Gillespie
相关产品推荐
相关产品推荐

