如何创建符合CAS登记号完整校验规则的TypeScript自定义类型
如何创建符合CAS登记号完整校验规则的TypeScript自定义类型
我明白你想实现一个严格贴合CAS登记号规则的TypeScript自定义类型——既要卡准格式、长度这些基础要求,还要能校验关键的校验和,这确实有点挑战,毕竟TypeScript的类型系统是静态的,要做这种计算型校验得花点巧思。先理清楚核心问题,再一步步解决:
先明确CAS的核心规则(避免走偏)
先把CAS的校验标准再梳理一遍,确保我们的类型能覆盖所有要求:
- 整体格式:
XXX-XX-X,其中:- 第一部分:2-7位数字,不能以0开头
- 第二部分:固定2位数字,允许前导零甚至全零(比如
50-00-0是合法的) - 第三部分:1位校验和数字,需通过前两部分计算得出
- 校验和计算逻辑:
- 拼接第一、第二部分为完整字符串(比如
151-21→15121) - 反转这个字符串(
15121→12151) - 每个字符乘以它的位置序号(从1开始),求和后取模10,结果必须等于第三部分
- 拼接第一、第二部分为完整字符串(比如
现有代码的问题分析
你之前用Range生成数字类型的思路有两个明显的坑:
- 类型爆炸导致编译错误:
Range<0, 9999999>会生成近千万个数字类型,TypeScript直接扛不住,报2589错误是必然的 - 无法处理字符串前导零:比如CAS第二部分的
01作为数字会被转成1,但CAS要求它必须是两位字符串,数字类型无法保留前导零 - 校验和完全没覆盖:用
Range只能限制数字范围,根本做不了计算型的校验
解决方案:用字符串模板类型+递归类型实现全校验
我们换个思路,直接在字符串层面做约束和计算,完全避开数字类型的缺陷:
第一步:定义基础辅助类型
先定义最基础的数字字符类型,方便后续组合:
type Digit = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'; type NonZeroDigit = Exclude<Digit, '0'>;
第二步:格式与长度的静态约束
用模板字符串类型直接定义各部分的格式,精准控制长度和前导零规则:
// 第一部分:2-7位数字,不能以0开头 type CASPartA = `${NonZeroDigit}${Digit}${Digit}?${Digit}?${Digit}?${Digit}?${Digit}?`; // 解释:1个非零数字 + 1-6个可选数字 → 总长度2-7,且首字符非零 // 第二部分:固定2位数字,允许前导零/全零 type CASPartB = `${Digit}${Digit}`; // 第三部分:1位数字(校验和) type CASPartC = Digit;
第三步:实现类型层面的校验和计算
这是最核心的部分,我们用递归类型实现字符串拼接、反转、求和取模:
// 辅助类型:拼接两个字符串 type Concat<A extends string, B extends string> = `${A}${B}`; // 辅助类型:反转字符串(递归实现) type Reverse<S extends string, Acc extends string = ''> = S extends `${infer First}${infer Rest}` ? Reverse<Rest, `${First}${Acc}`> : Acc; // 辅助类型:计算反转后的字符串的校验和(递归遍历每个字符,乘位置求和) type CalculateChecksum< S extends string, Position extends number = 1, Sum extends number = 0 > = S extends `${infer D}${infer Rest}` ? CalculateChecksum< Rest, // 位置+1:用数组长度模拟数字递增 [...Array<Position>, 0]['length'], // 求和:当前数字转成数字 + 之前的和 Sum + (D extends `${infer N extends number}` ? N * Position : 0) > : // 取模10,转成字符串类型 `${Sum extends infer T ? T extends number ? T % 10 : never : never}`;
第四步:组合成完整的ValidCAS类型
最后把所有约束组合起来,只允许符合校验和的CAS字符串:
// 先定义符合格式的CAS字符串类型(未校验校验和) type CASFormat = `${CASPartA}-${CASPartB}-${CASPartC}`; // 核心类型:从CASFormat中筛选出校验和合法的字符串 type ValidCAS<T extends string = CASFormat> = T extends `${infer A}-${infer B}-${infer C}` ? CalculateChecksum<Reverse<Concat<A, B>>> extends C ? T : never : never;
测试验证
现在用你的测试用例试试,完全符合预期:
// 合法CAS:不会报错 const validCAS1: ValidCAS = '151-21-3'; const validCAS2: ValidCAS = '50-00-0'; const validCAS3: ValidCAS = '134842-07-2'; const validCAS4: ValidCAS = '7664-93-9'; // 不合法CAS:编译报错(符合预期) const invalidCAS1: ValidCAS = '6123-1-1'; // 第二部分不是2位 const invalidCAS2: ValidCAS = '7647-01-1'; // 校验和错误 const invalidCAS3: ValidCAS = '0000000-00-0'; // 第一部分以0开头 const invalidCAS4: ValidCAS = '600000012-999-1'; // 格式错误(第二部分3位) const invalidCAS5: ValidCAS = 'abcd-ef-g'; // 非数字字符
关键优势
- 避免类型爆炸:用字符串模板代替大范围数字枚举,TypeScript编译速度快很多,不会报2589错误
- 精准覆盖所有规则:包括前导零、长度、校验和的全量校验
- 编译时校验:所有不合法的CAS都会在编写代码时直接报错,不需要等到运行时
内容来源于stack exchange
相关产品推荐
相关产品推荐

