[英]Infer return type based on variadic argument type
我正在构建一个包含大量命令和选项的大型 CLI 应用程序,其中一些在命令之间重用。 我正在使用yargs
库,我发现自己创建了如下所示的辅助函数:
const withFoo = (yargs: Argv) =>
yargs.option('foo', {
type: 'string',
});
const withBar = (yargs: Argv) =>
yargs.option('bar', {
type: 'string',
});
他们像这样使用它们:
const bazOptionsBuilder = (yargs: Argv) =>
withFoo(withBar(yargs)).option('baz', { type: 'string' });
现在,我想创建一个辅助函数来减少嵌套,我可以这样做:
const bazOptionsBuilder = withOptions(
withFoo,
withBar,
/* ... possibly more here */
(yargs) => yargs.option('baz', { type: 'string' }),
);
实现withOptions
函数相对简单,但我正在努力输入它。 到目前为止,我想出了:
import { Argv } from 'yargs';
type YargsBuilder<A extends Argv> = (y: Argv) => A;
type ArgsOf<T> = T extends Argv<infer A> ? A : never;
export function withOptions<A extends Argv<any>, B extends Argv<any>>(
a: YargsBuilder<A>,
b: YargsBuilder<B>,
): YargsBuilder<Argv<ArgsOf<A> & ArgsOf<B>>>;
export function withOptions<A extends Argv<any>, B extends Argv<any>, C extends Argv<any>>(
a: YargsBuilder<A>,
b: YargsBuilder<B>,
c: YargsBuilder<C>,
): YargsBuilder<Argv<ArgsOf<A> & ArgsOf<B> & ArgsOf<C>>>;
// ... and more ...
但这迫使我为withOptions
任意数量的参数withOptions
。 是否可以为withOptions
创建一个单一的类型签名,它会告诉withOptions
为YargsBuilder
s 中传递的任意数量,返回类型将是所有这些类型的联合?
为什么不将所有选项作为配置对象传递?
不同的语言支持不同的工具来提供可选参数,虽然在其他一些语言中构建器可能是你唯一的选择,但配置对象在 JS 中是更惯用的方法,yargs 也支持它:
可选的 .options() 可以采用将键映射到 opt 参数的对象。
var argv = require('yargs') .options({ 'f': { alias: 'file', demandOption: true, default: '/etc/passwd', describe: 'x marks the spot', type: 'string' } }) .argv ;
所以,在你的情况下,它变成了:
yargs.options({
'foo': {
type: 'string'
},
'bar': {
type: 'string'
},
'baz': {
type: 'string'
},
})
您可以为常量提取一些选项:
const fooOption = {
'foo': {
type: 'string'
}
} as const;
const barOption = {
'bar': {
type: 'string'
}
} as const;
const bazOption = {
'baz': {
type: 'string'
}
} as const;
yargs.options({
...fooOption,
...barOption,
...bazOption
});
是的,这是可能的,但并不简单。 您可能需要考虑按照其他人的建议以不同的方式构建代码; 我对yargs
不熟悉,所以我不能对此发表评论。
此解决方案使用两个一次性类型别名,您可以将它们复制并粘贴到它们的用法中以删除它们,或者根据您可能希望在函数中输入内容的方式对它们进行不同的构建。
// https://stackoverflow.com/a/50375286/2398020
type UnionToIntersection<U> =
(U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never;
type YargsBuilder<A extends Argv> = (y: Argv) => A;
type _getYargBuildersTypeUnion<A extends YargsBuilder<any>[]> = {
[K in keyof A]: A[K] extends YargsBuilder<Argv<infer T>> ? T : never
}[Exclude<keyof A, keyof []>];
type _withOptionsResult<A extends YargsBuilder<any>[]> =
YargsBuilder<Argv<UnionToIntersection<_getYargBuildersTypeUnion<A>>>>
export function withOptions<A extends YargsBuilder<any>[]>(...args: A): _withOptionsResult<A>;
export function withOptions(...args: YargsBuilder<any>[]): YargsBuilder<any> {
return null as any; // TODO
}
测试用法:
// YargsBuilders
const A0 = (arg: Argv<{ x: string }>) => arg;
const A1 = (arg: Argv<{ five: 5 }>) => arg;
const A2 = (arg: Argv<{ ids: number[] }>) => arg;
const X = withOptions(A0, A1, A1, A2);
// const X: YargsBuilder<Argv<{
// x: string;
// } & {
// five: 5;
// } & {
// ids: number[];
// }>>
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.