共计 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
为基拓展子类时可能很难失去很好的类型反对; - 每步操作须要在上步操作的返回值上执行,以应用正确残缺的类型信息。