关于typescript:TS-分析字符串实现-Commanderjs-自动强类型

78次阅读

共计 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 的介绍中有所体现,因为类型定义中每次都是返回一个新实例,

  • CommandOptionArgument 为基拓展子类时可能很难失去很好的类型反对;
  • 每步操作须要在上步操作的返回值上执行,以应用正确残缺的类型信息。

正文完
 0