如何在 Typescript 中定义正则表达式匹配的字符串类型?

How to define a regex-matched string type in Typescript?

是否可以定义一个接口,其中包含一些关于字符串格式的信息?举个例子:

interface timeMarkers{
    markerTime: string[]        
};

一个例子是:

{
   markerTime: ["0:00","1:30", "1:48"]                   
}

我的问题:有没有一种方法可以定义 markerTime 的类型,使字符串值必须始终匹配此正则表达式,而不是将其简单地声明为 string[] 并从那里开始?

var reg = /[0-9]?[0-9]:[0-9][0-9]/;

无法定义这样的类型。 GitHub 上有一个 proposal 来支持这一点,但目前似乎不是优先考虑的问题。对其进行投票,也许团队可能会将其包含在未来的版本中。

编辑

从 4.1 开始,您可以定义一个类型来验证字符串,而无需实际定义所有选项:

type MarkerTime =`${number| ''}${number}:${number}${number}`

let a: MarkerTime = "0-00" // error
let b: MarkerTime = "0:00" // ok
let c: MarkerTime = "09:00" // ok

Playground Link

我现在也在寻找类似的功能!

最后我想到了这个: 设置稍微复杂一点的dev-environment不就可以得到这个运行吗?也许您可以使用 file-watcher 来触发 tsc 并查找 TypeError 事件来更新您的 *d.ts 文件。

我的意思是:

export type superRegexType = 'type-1' | 'type-2' | '/type-/';

作为钩子的东西(基本建议):

const onTypeError = (err: Error, nameOfTypeSuperRegexType: string) => {
  const myTypesFile = require('fs').readFileSync(`path/to/\*d.ts`) as string;
  const searchFor = `export type ${nameOfTypeSuperRegexType} =`;
  const getDefinition = (inMyTypesFile: string, searchFor: string) => {
    const typeDefinitionString = inMyTypesFile.split(searchFor)[0].split(';')[0] + ';';
    const stringsInThere = typeDefinitionString.split(' | ').map(_str => _str.trim());
    const myRegexStr = stringsInThere.pop();
    return {
      oldTypeDefinitionString: typeDefinitionString,
      stringsInThere,
      myRegexStr,
      myRegex: new RegExp(myRegexStr)
    };
  };
  const myTypeDefinition = getDefinition(myTypesFile, searchFor);

  const shouldDynamicallyAddType = myTypeDefinition.myRegex.exec(err.message);
  if (!shouldDynamicallyAddType) {
    console.log("this is a real TypeError");
    console.error(err);
    return;
  } else {
    const indexInErrMessage = shouldDynamicallyAddType.index;
    const _endIdx = err.message.indexOf('\'');
    const typeToAdd = err.message.slice(indexInErrMessage, _endIdx).trim();
    myTypeDefinition.stringsInThere.push(typeToAdd);
    const updatedTypeDefinitionString = `${searchFor} ${myTypeDefinition.stringsInThere.join(' | ')} ${myTypeDefinition.myRegexStr};`;
    myTypesFile.replace(myTypeDefinition.oldTypeDefinitionString, updatedTypeDefinitionString);
    // --> save that new d.ts and return hopefully watch the lint-error disappearing
  }
}

也许这种解决方案可以让您在编译时根据您的 RegEx 动态添加类型。

你怎么看?

在该语言可以使用正则表达式类型之前,您现在可以在 TS 4.1 中使用 template literal types

让我参考问题示例并说明,如何为名为 Time 的时间受限 string 类型建模。 Time 需要格式为 hh:mm 的字符串(例如 "23:59"),这里是为了简化。

第 1 步:定义 HHMM 类型

将以下代码粘贴到您的浏览器 Web 控制台中:
Array.from({length:24},(v,i)=> i).reduce((acc,cur)=> `${acc}${cur === 0 ? "" : "|"}'${String(cur).padStart(2, 0)}'`, "type HH = ")
Array.from({length:60},(v,i)=> i).reduce((acc,cur)=> `${acc}${cur === 0 ? "" : "|"}'${String(cur).padStart(2, 0)}'`, "type MM = ")
生成的结果,我们可以在 TS 中用作类型:
type HH = '00'|'01'|'02'|'03'|'04'|'05'|'06'|'07'|...|'22'|'23'
type MM = '00'|'01'|'02'|'03'|'04'|'05'|'06'|'07'|...|'58'|'59'

第 2 步:声明 Time

type Time = `${HH}:${MM}`

就这么简单。

第 3 步:一些测试

const validTimes: Time[] = ["00:00","01:30", "23:59", "16:30"]
const invalidTimes: Time[] = ["30:00", "23:60", "0:61"] // all emit error

这里有一个 live code example 可以玩 Time

根据@bela53 的回答,但更简单,我们可以做一个非常简单的解决方案,类似于@Titian 但没有缺点:

type HourPrefix = '0'|'1'|'2';
type MinutePrefix = HourPrefix | '3'|'4'|'5';
type Digit = MinutePrefix |'6'|'7'|'8'|'9';

type Time = `${HourPrefix | ''}${Digit}:${MinutePrefix}${Digit}`

const validTimes: Time[] = ["00:00","01:30", "23:59", "16:30"]
const invalidTimes: Time[] = ["30:00", "23:60", "0:61"] // all emit error

警告:TypeScript 可以使用 @bela53 方法处理的内容有限...

基于 @bela53 我尝试了 IPv4 地址的以下类型定义,导致 "TS2590: Expression produces a union type that is too complex to represent." 此定义导致 IntelliJ 消耗大量还有很多 CPU 次我忽略了 TypeScript 错误并尝试在它仍然有效的前提下进行构建(我最终需要终止并重新启动 IntelliJ)。

type segment = '0'|'1'|'2'|'3'|'4'|'5'|'6'|'7'|'8'|'9'|'10'|'11'|'12'|'13'|'14'|'15'|'16'|'17'|'18'|'19'|'20'|'21'|'22'|'23'|'24'|'25'|'26'|'27'|'28'|'29'|'30'|'31'|'32'|'33'|'34'|'35'|'36'|'37'|'38'|'39'|'40'|'41'|'42'|'43'|'44'|'45'|'46'|'47'|'48'|'49'|'50'|'51'|'52'|'53'|'54'|'55'|'56'|'57'|'58'|'59'|'60'|'61'|'62'|'63'|'64'|'65'|'66'|'67'|'68'|'69'|'70'|'71'|'72'|'73'|'74'|'75'|'76'|'77'|'78'|'79'|'80'|'81'|'82'|'83'|'84'|'85'|'86'|'87'|'88'|'89'|'90'|'91'|'92'|'93'|'94'|'95'|'96'|'97'|'98'|'99'|'100'|'101'|'102'|'103'|'104'|'105'|'106'|'107'|'108'|'109'|'110'|'111'|'112'|'113'|'114'|'115'|'116'|'117'|'118'|'119'|'120'|'121'|'122'|'123'|'124'|'125'|'126'|'127'|'128'|'129'|'130'|'131'|'132'|'133'|'134'|'135'|'136'|'137'|'138'|'139'|'140'|'141'|'142'|'143'|'144'|'145'|'146'|'147'|'148'|'149'|'150'|'151'|'152'|'153'|'154'|'155'|'156'|'157'|'158'|'159'|'160'|'161'|'162'|'163'|'164'|'165'|'166'|'167'|'168'|'169'|'170'|'171'|'172'|'173'|'174'|'175'|'176'|'177'|'178'|'179'|'180'|'181'|'182'|'183'|'184'|'185'|'186'|'187'|'188'|'189'|'190'|'191'|'192'|'193'|'194'|'195'|'196'|'197'|'198'|'199'|'200'|'201'|'202'|'203'|'204'|'205'|'206'|'207'|'208'|'209'|'210'|'211'|'212'|'213'|'214'|'215'|'216'|'217'|'218'|'219'|'220'|'221'|'222'|'223'|'224'|'225'|'226'|'227'|'228'|'229'|'230'|'231'|'232'|'233'|'234'|'235'|'236'|'237'|'238'|'239'|'240'|'241'|'242'|'243'|'244'|'245'|'246'|'247'|'248'|'249'|'250'|'251'|'252'|'253'|'254'|'255';
export type ipAddress = `${segment}.${segment}.${segment}.${segment}`;

我不确定是否有任何解决方法。

type D1 = 0|1;
type D3 = D1|2|3;
type D5 = D3|4|5;
type D9 = D5|6|7|8|9;

type Hours = `${D9}` | `${D1}${D9}` | `2${D3}`;
type Minutes = `${D5}${D9}`;
type Time = `${Hours}:${Minutes}`;

紧凑的解决方案汇总了 @bela53 and @yoel halb 的想法。

此解决方案有 2039 个 Time 类型的枚举成员。

Ts Playground

我的 2 美分


type digit01 = '0' | '1';
type digit03 = digit01 | '2' | '3';
type digit05 = digit03 | '4' | '5';
type digit09 = digit05 | '6' | '7' | '8' | '9';

type minutes = `${digit05}${digit09}`;
type hour = `${digit01 | ''}${digit09}` | `2${digit03}`;

type MarkerTime = `${hour}:${minutes}`;

const ok: Record<string, MarkerTime> = {
  a: '0:00',
  b: '09:00',
  c: '23:59',
};

const notOk: Record<string, MarkerTime> = {
  a: '0-00',
  b: '24:00',
  c: '93.242:942.23',
};