TypeScript中是否支持定义窄化整数范围的自定义类型?
TypeScript 对number类型实现YYYYMMDD格式窄化的方案
首先明确结论:TS 原生没有和字符串模板字面量对等的、可以直接对number类型做正则格式匹配的类型能力,数字类型在TS类型系统里没有对应的模板匹配语法,没法像字符串那样直接写个类型规则就自动过滤所有不符合YYYYMMDD格式的数字。
目前工程上可行的方案有3种,各有适用场景:
- 品牌类型(Branded Type)方案(生产环境首选)
这是目前TS生态里处理这类特殊格式数值的通用方案,核心逻辑是给普通number打一个唯一的类型标记,从类型层面把「符合YYYYMMDD格式的整数日期」和普通数字做区分,所有该类型的值都必须通过指定的校验函数生成,从入口拦住非法值。
示例代码:
这个方案的缺点是没法在写字面量的时候直接做静态校验,必须走构造函数转换,但胜在性能好、能做完整的日期合法性校验(包括闰年、大小月判断),完全满足生产环境需求。// 给number加唯一品牌标记,和普通number做类型区分 type YYYYMMDDDate = number & { __dateBrand: 'YYYYMMDD' }; // 校验构造函数,只有通过格式、合法性校验的数字才能转为目标类型 function createYYYYMMDD(val: number): YYYYMMDDDate { // 先筛掉明显不在日期范围内的值 if (val < 19000101 || val > 99991231) throw new Error('日期值超出合法区间'); const year = Math.floor(val / 10000); const month = Math.floor((val % 10000) / 100); const day = val % 100; // 校验月份范围 if (month < 1 || month > 12) throw new Error('月份值非法'); // 校验对应月份的日期合法性(自动兼容闰年、大小月) const maxDay = new Date(year, month, 0).getDate(); if (day < 1 || day > maxDay) throw new Error('日期值非法'); return val as YYYYMMDDDate; } // 合法用法 const today = createYYYYMMDD(20220603); // 类型为YYYYMMDDDate // 直接给普通数字赋值会报类型错误 // const badDate: YYYYMMDDDate = 20221301; // 类型校验不通过 - 全量字面量联合类型(完全不推荐)
理论上你可以通过TS递归类型,把1900年到9999年所有合法的YYYYMMDD日期值拼成一个联合类型,比如type YYYYMMDDDate = 19000101 | 19000102 | ...,实现字面量层面的自动校验,但这个方案没有任何实用价值:合法日期总量接近300万,远超出TS类型实例化的递归深度上限,就算强行修改TS配置生成了这个类型,TS的类型检查性能会直接崩溃,IDE补全、类型提示都会完全卡死。
- 转字符串做模板校验(仅适用于字面量场景)
如果你需要在写字面量的时候就获得格式校验提示,可以先把数字字面量转成字符串,复用字符串的模板字面量校验规则,校验通过再映射回数字类型:
这个方案的局限性很明显:只能做最基础的格式校验,没法判断大小月、闰年这类逻辑,而且只对明确写死的数字字面量生效,对动态传入的number变量没有约束能力,只能作为辅助校验手段。// 先定义字符串版本的基础YYYYMMDD格式 type StrYYYYMMDD = `${number}${number}${number}${number}${'01'|'02'|'03'|'04'|'05'|'06'|'07'|'08'|'09'|'10'|'11'|'12'}${number}${number}`; // 把数字转成字符串做格式校验,符合就返回原数字类型,否则返回never type ValidDateNum<T extends number> = `${T}` extends StrYYYYMMDD ? T : never; // 字面量场景下可以直接识别非法值 type GoodDate = ValidDateNum<20220603>; // 类型为20220603 type BadDate = ValidDateNum<20221301>; // 类型为never
内容的提问来源于stack exchange,提问作者Sasgorilla
相关产品推荐
相关产品推荐

