如何在typeScript中定义仅接受4的倍数的数字类型
什么是TypeScript 类型声明
类型声明就是给变量设置了类型,使得变量只能存储某种类型的值
类型声明的作用:
- 通过类型声明可以指定 TS 中的变量(参数,形参)的类型
- 指定类型之后,再给变量赋值,会自动进行检测,如果符合则赋值,不符合则会抛出错误
TS 自带类型判断的功能:
当对变量的声明和赋值同时进行时,TS 编译器会自动判断变量的类型,因此当声明和赋值同时进行的时候,可以省略掉类型声明。
想进一步了解的小伙伴可以参考TypeScript 中文手册
对类型声明有一定了解后,我们继续研究如何才能在typeScript中定义仅接受4的倍数的类型?
可以直接将下方代码粘贴在TypeScript演练场
方法1
网上流传的一种方法如下:
tsx
type IsMultipleOfFour<T> = T extends number ? (T % 4 extends 0 ? T : never) : never;
function foo<T extends IsMultipleOfFour<T>>(value: T): void {
// ...
}
foo(4); // OK
foo(8); // OK
foo(3); // Error: 类型"3"的参数不能赋给类型"IsMultipleOfFour<3>"的参数。
此方法看似合理,但是有报错:
'never' only refers to a type, but is being used as a value here.
经过一番度娘,Gpt... 重新修改定义:
tsx
type IsValidSize<T extends number, R extends number[] = []> = T extends R['length']
? R['length'] extends 0
? T
: never
: IsValidSize<T, [0, 0, 0, 0, ...R]>;
type IsMultipleOfFour<T extends number> = T extends IsValidSize<infer U> ? U : never;
type CheckIsMultipleOfFour<T extends number> = T extends IsMultipleOfFour<T> ? T : never;
function foo<T extends number>(value: CheckIsMultipleOfFour<T>): void {
console.log(value)
}
解决了never
的报错,尝试运行,又出现了新的报错,如下:
Type instantiation is excessively deep and possibly infinite.
原因:类型实例化过于深入且可能无限,即可能会导致递归深度过深的错误。
这种错误可能会导致编译器性能下降或内存耗尽,因此TypeScript会限制类型实例化的深度。当达到特定深度时,编译器将显示此错误。
避免递归深度过深:
tsx
type IsMultipleOfFour<T extends number> = T extends 4 | 8 | 12 | 16 | 20 | 24 | 28 | 32 | 36 | 40 | 44 | 48 | 52 | 56 | 60 | 64 | 68 | 72 | 76 | 80 | 84 | 88 | 92 | 96 | 100 ? T : never;
function foo<T extends number>(value: IsMultipleOfFour<T>): void {
console.log(value)
}
foo(4); // OK
foo(8); // OK
foo(3); // Error: 类型"3"的参数不能赋给类型"IsMultipleOfFour<3>"的参数。
尝试运行...成功!
方法2
正则表达式 (一种用于匹配和操作文本的强大工具。)不妨尝试javaScript模板字符串${}
。它用于进行字符串模式匹配和组合。
让它只能是4
的倍数,不妨假设:
1. 当数字只有一位,很明显只有0,4,8满足,类型定义如下:
tsx
type MuiltpleOf4L1<T extends number> = `${T}` extends `0` | '4' | '8' ? T : never;
2. 当数字只有两位,需要考虑个位和十位,类型定义如下:
tsx
type MuiltpleOf4L2<T extends number> = `${T}` extends `${2 | 4 | 6 | 8}${0 | 4 | 8}` | `${1 | 3 | 5 | 7 | 9}${2 | 6}` ? T : never;
3. 当数字是三位,类型定义如下:
tsx
type MuiltpleOf4L3<T extends number> = `${T}` extends `${number}${0 | 2 | 4 | 6 | 8}${0 | 4 | 8}` | `${number}${1 | 3 | 5 | 7 | 9}${2 | 6}` ? T : never;
到这里不禁有人提出质疑,这么列举下去,四位数、五位数、六位数...仍然是无穷尽,还是解决不了呢。
实则不然,三位数的情况已经涵盖了三位及以上所有位数。
整合之后能得到了一个专门用于判断是否是4的倍数的类型吗?
tsx
type MuiltpleOf4L1<T extends number> = `${T}` extends `0` | '4' | '8' ? T : never;
type MuiltpleOf4L2<T extends number> = `${T}` extends `${2 | 4 | 6 | 8}${0 | 4 | 8}` | `${1 | 3 | 5 | 7 | 9}${2 | 6}` ? T : never;
type MuiltpleOf4L3<T extends number> = `${T}` extends `${number}${0 | 2 | 4 | 6 | 8}${0 | 4 | 8}` | `${number}${1 | 3 | 5 | 7 | 9}${2 | 6}` ? T : never;
type MuiltpleOf4<T extends number> = MuiltpleOf4L1<T> | MuiltpleOf4L2<T> | MuiltpleOf4L3<T>;
看似十分合理,尝试运行...
tsx
const foo = <Num extends number>(num: MuiltpleOf4<Num>) => num
foo(4); // OK
foo(20); // OK
foo(120); // OK
foo(1000);// OK
很明显是适用的。大获成功!
方法3(扩展)
先实现判断一个数是否是另一个数的整数倍,再实现判断一个数是否是4的整数倍。
如何判断一个数T是否是另一个数V的整数倍?
- 计算
T
除以V
的结果(整除),得到一个整除结果
。 - 计算
整除结果
与V
的乘积,得到一个乘积结果
。 - 计算
T
与乘积结果
之间的差值。 - 判断差值是否为
0
。如果差值为0
,则表示T
是V
的倍数。
思路有了,接下来尝试定义一些类型,使之能够判断一个数 T
是否能被4
整除。步骤大致如下:
1、 定义名为 MakeArr
的类型
用于生成一个特定长度(ArrLen)的数组,数组的每个元素值为 FillNumber
。MakeArr
接受三个类型参数:
- FillNumber:一个扩展自 number 的类型,表示要填充到数组的值。
- ArrLen:表示目标数组的长度。
- ExistArr:一个扩展自 number[] 的类型,表示当前已经生成的数组。
tsx
type MakeArr<FillNumber extends number, ArrLen, ExistArr extends number[]> = ExistArr['length'] extends ArrLen ? ExistArr : MakeArr<FillNumber, ArrLen, [FillNumber, ...ExistArr]>
2、定义名为 Arr
的类型
用于生成一个特定长度(ArrLen)的数组,Arr
接受一个类型参数:表示目标数组的长度。
tsx
type Arr<ArrLen extends number> = number extends ArrLen ? 1[] : MakeArr<1, ArrLen, []>
3、定义名为 Minus
的类型
用于计算两个数字类型 a
和 b
的差值
tsx
type Minus<a extends number, b extends number> = Arr<a> extends [...LeftPart: Arr<b>, ...LeftPart: infer ValuePart] ? ValuePart['length'] : never
4、定义名为 PartMultiple
的类型
用于计算两个数字类型 a
和 b
的乘积。PartMultiple
是一个递归类型,接受三个类型参数:
- a:一个扩展自 number 的类型,表示乘数。
- b:一个扩展自 number 的类型,表示被乘数。
- pre:一个扩展自 number[] 的类型,表示之前累计的结果数组。
tsx
type PartMultiple<a extends number, b extends number, pre extends number[]> = b extends 0 ? pre['length'] : PartMultiple<a, Minus<b, 1>, [...Arr<a>, ...pre]>
5、定义名为 Multiple
的类型
用于是计算两个数字类型 a
和 b
的乘积。
tsx
type Multiple<a extends number, b extends number> = PartMultiple<a, b, [
6、定义名为 PartDivide
的类型
用于计算两个数字类型 a
和 b
的整除结果。PartDivide
是一个递归类型,接受三个类型参数:
- a:一个扩展自 number 的类型,表示被除数。
- b:一个扩展自 number 的类型,表示除数。
- pre:一个扩展自 Array<number[]> 的类型,表示之前累计的结果数组。
tsx
type PartDivide<a extends number, b extends number, pre extends Array<number[]>> = a extends 0 ? pre['length'] : PartDivide<Minus<a, b>, b, [Arr<b>, ...pre]>
7、定义名为 Divide
的类型
用于是计算两个数字类型 a
和 b
的整除结果。
tsx
type Divide<a extends number, b extends number> = PartDivide<a, b, []>
8、定义名为 IsMultipleOfSomeNum
的类型
用于判断一个数字类型 T
是否是另一个数字类型 V
的倍数。
tsx
type IsMultipleOfSomeNum<T extends number,V extends number> =Minus<T,Multiple<Divide<T, V>,V>> extends 0? true:false;
9、定义名为 IsMultipleOfFour
的类型
用于判断一个数字类型 T
是否是数字4
的倍数。
tsx
type IsMultipleOfFour<T extends number> = IsMultipleOfSomeNum<T,4>
整合之后
tsx
type MakeArr<FillNumber extends number, ArrLen, ExistArr extends number[]> = ExistArr['length'] extends ArrLen ? ExistArr : MakeArr<FillNumber, ArrLen, [FillNumber, ...ExistArr]>
type Arr<ArrLen extends number> = number extends ArrLen ? 1[] : MakeArr<1, ArrLen, []>
type Minus<a extends number, b extends number> = Arr<a> extends [...LeftPart: Arr<b>, ...LeftPart: infer ValuePart] ? ValuePart['length'] : never
type PartMultiple<a extends number, b extends number, pre extends number[]> = b extends 0 ? pre['length'] : PartMultiple<a, Minus<b, 1>, [...Arr<a>, ...pre]>
type Multiple<a extends number, b extends number> = PartMultiple<a, b, []>
type PartDivide<a extends number, b extends number, pre extends Array<number[]>> = a extends 0 ? pre['length'] : PartDivide<Minus<a, b>, b, [Arr<b>, ...pre]>
type Divide<a extends number, b extends number> = PartDivide<a, b, []>
/** 判断T是否是V的整数倍 */
type IsMultipleOfSomeNum<T extends number,V extends number> =Minus<T,Multiple<Divide<T, V>,V>> extends 0? true:false;
const x : IsMultipleOfSomeNum<25,4> = false;
const y : IsMultipleOfSomeNum<24,4> = true;
const z : IsMultipleOfSomeNum<25,5> = true;
/** 判断T是否是4的整数倍 */
type IsMultipleOfFour<T extends number> = IsMultipleOfSomeNum<T,4>
// 使用示例
const value1: IsMultipleOfFour<8> = true; // 正确
const value2: IsMultipleOfFour<7> = false; // 正确
const value3: IsMultipleOfFour<16> = true; // 正确
const value4: IsMultipleOfFour<17> = false; // 正确
虽然定义了很多类型,实现起来十分麻烦,写起来十分复杂,但是结果是可行的。成功!
灵感来源于以下文章 #使用 TS 类型实现自然数加减乘除
方法4
一种最简单的方法直接列举一些常用的数据,比如前100,如下定义。
tsx
type IsMultipleOfFour = 4 | 8 | 12 | 16 | 20 | 24 | 28 | 32 | 36 | 40 | 44 | 48 | 52 | 56 | 60 | 64 | 68 | 72 | 76 | 80 | 84 | 88 | 92 | 96 | 100
总结
在 TypeScript 中,实现这样的要求相对来说比较困难,因为它涉及到算术运算,而 TypeScript 的类型系统主要用于静态类型检查,而不是执行运算。TypeScript一般是是用来约束变量的形状的,可以约束它是number,但直接约束是4的倍数是非常难的。
若小伙伴们有什么更好的方法,欢迎在评论区留言🤞🤞🤞