共计 2081 个字符,预计需要花费 6 分钟才能阅读完成。
数字误认作字符,字符串误认作数组,Promise 没有 await 就取值,这些问题在 TypeScript 里把每个类型都定义对了就不会呈现,还会有很好的编辑提醒。
但写命令行工具,定义一个某类型的选项时,一边要传参如 .option("-d, --dev")
,一边要标注类型如 {dev: boolean}
,两个中央须要手动同步。繁琐易错,怎么办?TypeScript 早在 4.1 就能够设计剖析字符串生成类型了。
当初,通过 @commander-js/extra-typings
就能够主动失去字符串中设计的命令构造。
import {program} from '@commander-js/extra-typings';
program
.argument("<input>")
.argument("[outdir]")
.option("-c, --camel-case")
.action((input, outputDir, option) => {
// input 是 string
// outputDir 是 string | undefined
// option 是 {camelCase?: true | undefined}
});
本文介绍 @commander-js/extra-typings
用到的关键技术。
必须 / 可选,单个 / 数个
必须 / 可选参数 往往形如 <xxx>
/ [xxx]
,其中 xxx
为参数名。
参数名以 ...
结尾时,示意该参数能够蕴含多个取值。
对于这样的字符串,应用 extends
关键字即可设计条件对应类型。
// S 取 "<arg>" 得 true
// S 取 "[arg]" 得 false
type IsRequired<S extends string> =
S extends `<${string}>` ? true : false;
// S 取 "<arg...>" 得 true
// S 取 "<arg>" 得 false
type IsVariadic<S extends string> =
S extends `${string}...${string}` ? true : false;
选项名
选项名时常有精简写法,如 -r
可能示意 --recursive
。作为命令行选项时通常应用 -
配合小写字母的命名形式,在代码中则罕用驼峰命名法。
对于应用 逗号 + 空格 来提前搁置精简写法的选项,能够应用 infer
关键字推导模板文字递归化简。
// S 取 "-o, --option-name" 得 "option-name"
type OptionName<S extends string> =
S extends `${string}, ${infer R}`
? OptionName<R> // 去除逗号,空格,及之前的内容
: S extends `-${infer R}`
? OptionName<R> // 去除结尾的 "-"
: S;
将短线 -
转换为驼峰命名,能够联合 Capitalize
。
// S 取 "option-name" 得 "optionName"
type CamelCase<S extends string> =
S extends `${infer W}-${infer R}`
? CamelCase<`${W}${Capitalize<R>}`>
: S;
变长参数
参数长度不定的函数,参数能够通过开展类型元组来定义类型。
type Args = [boolean, string, number];
type VarArgFunc = (...args: Args) => void;
const func: VarArgFunc = (arg1, arg2, arg3) => {
// arg1 为 boolean
// arg2 为 string
// arg3 为 number
};
类型元组能够贮存在类参数中,并同样通过开展运算符 ...
来联合新元素。
declare class Foo<Args extends unknown[] = []> {concat<T>(arg: T): Foo<[...Args, T]>;
run((...args: Args) => void): void;
}
const foo = new Foo()
.concat(1)
.concat("str")
.concat(true);
foo.run((arg1, arg2, arg3) => {
// arg1 为 number
// arg2 为 string
// arg3 为 boolean
});
限度
实现 @commander-js/extra-typings
遇到的最大阻碍,在于对 this
信息的保留。在变长参数一节,每次 concat
增加信息都须要返回一个新实例,能不能应用 &
或 mixin
等其余技术联合 this
呢?目前实测后果是 不能,TS 在这类实测中,非常容易报错或卡死,不卡死时在某些中央会提醒 TS 查看陷入死循环,不卡死不报错时往往是陷入了无响应的状态。
相干记录能够在原实现 PR #1758 · tj/commander.js 中找到。
这样的限度也在 @commander-js/extra-typings
的介绍中有所体现,因为类型定义中每次都是返回一个新实例,
- 以
Command
、Option
、Argument
为基拓展子类时可能很难失去很好的类型反对; - 每步操作须要在上步操作的返回值上执行,以应用正确残缺的类型信息。