如果您需要支持的只是getConfig()
的callers的强类型,则可以在path
参数的类型K
中将其设置为generic,并使用the Parameters<T>
和the ReturnType<T>
实用程序类型来表示输入/输出关系:
declare function getConfig<K extends keyof Config>(
path: K,
...args: Parameters<Config[K]>
): ReturnType<Config[K]>;
它的工作方式与预期一致:
getConfig(path.A) // okay
getConfig(path.B, { id: "xxx" }) // error, expected 1 arg but got 2
getConfig(path.C) // error // error, expected 2 args but got 1
getConfig(path.C, { active: true }) // okay
getConfig(path.C, { id: "xxx" }) // error, {id: string} is not {active: boolean}
但是,您会发现编译器将无法验证此函数的implementation是否正确:
function getConfig<K extends keyof Config>(
path: K,
...args: Parameters<Config[K]>
): ReturnType<Config[K]> {
return config[path](...args); // error!
// ---------------> ~~~~~~~ A spread argument must have a tuple type
// ~~~~~~~~~~~~~~~~~~~~~~~~~~ <-- unknown is not assignable to type...
}
这是目前对类型脚本的限制;像Parameters<Config[K]>
和ReturnType<Config[K]>
这样的泛型conditional types对于编译器来说基本上是不透明的.您仍然可以使用这些类型,但您需要在实现中使用type assertions来 suppress 错误,并注意您已经正确地完成了工作,因为编译器在这里无法提供帮助:
function getConfig<K extends keyof Config>(
path: K,
...args: Parameters<Config[K]>
) {
return (config as any)[path](...args) as ReturnType<Config[K]>
// ^^^^^^ <-- assert ---> ^^^^^^^^^^^^^^^^^^^^^^^^
}
如果希望编译器遵循通用逻辑,则需要重构为对通用mapped types使用indexed accesses,如microsoft/TypeScript#47109中所述:
type ConfigArgs = { [K in keyof Config]: Parameters<Config[K]> }
type ConfigRet = { [K in keyof Config]: ReturnType<Config[K]> }
const _config: { [K in keyof Config]:
(...args: ConfigArgs[K]) => ConfigRet[K]
} = config;
function getConfig<K extends keyof Config>(
path: K,
...args: ConfigArgs[K]
) {
return _config[path](...args) // okay, seen as ConfigRet[K]
}
在这里,我们将ConfigArgs
和ConfigRet
都定义为Config
上的映射类型,然后将_config
变量定义为函数的映射类型,这些函数接受ConfigArgs[K]
并 for each 键K
返回ConfigRet[K]
.编译器允许我们将config
赋给这个变量,因为它能够将映射的类型扩展为与config
相同的特定类型.
现在可以看到,getConfig()
函数正在对类型为(...args: ConfigArgs[K]) => ConfigRet[K]
的值_config[path]
执行操作,因此它接受类型为ConfigArgs[K]
的REST参数并生成类型为ConfigRet[K]
的输出.
因此,一切仍在调用方工作,并且实现也是类型判断的.
请注意,typeof config
和typeof _config
在概念上没有区别,但是编译器认为typeof _config
以一种它看不到的方式表示输入和输出之间的一般关系.
Playground link to code个