我正在构建一个包含许多命令和选项的大型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
创建一个单一的类型签名,该签名将告诉typescript输入YargsBuilder
的任意数量,返回类型将是所有这些类型的并集?
答案 0 :(得分:1)
为什么不将所有选项作为配置对象传递?
不同的语言支持提供可选参数的不同工具,虽然在某些其他语言中,构建器可能是您的唯一选择,但在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
});
答案 1 :(得分:1)
是的,有可能但并不简单。您可能要考虑其他人建议的不同代码结构;我对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[];
// }>>