手撕TypeScript高级类型:Trunc数值截断完全指南
你是否还在为TypeScript类型体操中的数值处理头疼?面对带小数点的数字和字符串混合输入,如何精准提取整数部分?本文将通过实现Trunc类型工具,带你掌握模板字面量类型(Template Literal Types)、条件类型(Conditional Types)和类型推断(Type Inference)的实战技巧,彻底解决数值截断难题。
问题定义:TypeScript版Math.trunc
Trunc类型工具需要实现与Math.trunc相同的功能:接收数字或字符串类型的输入,返回其整数部分的字符串表示。与JavaScript不同的是,TypeScript类型系统中没有运行时运算,我们需要通过类型体操完成这项挑战。
核心需求分析
从测试用例中可以看出,Trunc需要处理以下场景:
// 基本数字输入
Trunc<0.1> → '0'
Trunc<1.234> → '1'
Trunc<12.345> → '12'
// 负数处理
Trunc<-5.1> → '-5'
Trunc<'-10.234'> → '-10'
// 特殊字符串输入
Trunc<'.3'> → '0' // 缺失整数部分
Trunc<'-.3'> → '-0' // 负号+缺失整数部分
Trunc<'1.234'> → '1' // 字符串数字
// 整数直接返回
Trunc<10> → '10'
类型挑战复杂度分析
这个问题看似简单,实则涉及TypeScript类型系统的多个高级特性:
实现思路拆解
类型转换流程图
分步实现策略
1. 统一输入类型为字符串
首先需要将数字类型转换为字符串类型,以便使用模板字面量类型进行处理:
type NumberToString<T> = T extends number ? `${T}` : T;
2. 符号分离与处理
接下来需要处理可能存在的负号:
type SplitSign<S> = S extends `-${infer Rest}`
? { sign: '-'; value: Rest }
: { sign: ''; value: S };
3. 小数部分截断核心逻辑
这是整个实现的关键,需要处理以下几种情况:
- 包含小数点的字符串(如"12.34")
- 以小数点开头的字符串(如".3")
- 纯整数字符串(如"10")
type TruncateDecimal<T> =
T extends `${infer IntegerPart}.${infer _DecimalPart}`
? IntegerPart extends '' ? '0' : IntegerPart // 处理".3"情况
: T extends '' ? '0' // 处理空字符串情况
: T; // 纯整数情况
完整实现代码
综合以上分析,我们可以写出完整的Trunc类型工具:
type Trunc<T> =
// 第一步:统一转换为字符串类型
NumberToString<T> extends infer Str
? Str extends string
// 第二步:分离符号
? SplitSign<Str> extends infer SignInfo
? SignInfo extends { sign: infer S; value: infer V }
// 第三步:截断小数部分
? TruncateDecimal<V> extends infer Integer
? Integer extends string
// 第四步:组合结果
? `${S}${Integer}`
: never
: never
: never
: never
: never
: never;
// 辅助类型定义
type NumberToString<T> = T extends number ? `${T}` : T;
type SplitSign<S> = S extends `-${infer Rest}` ? { sign: '-'; value: Rest } : { sign: ''; value: S };
type TruncateDecimal<T> =
T extends `${infer IntegerPart}.${infer _DecimalPart}`
? IntegerPart extends '' ? '0' : IntegerPart
: T extends '' ? '0'
: T;
完整测试用例验证
为确保实现的正确性,我们需要验证所有测试场景:
// 测试用例
type cases = [
Expect<Equal<Trunc<0.1>, '0'>>,
Expect<Equal<Trunc<0.2>, '0'>>,
Expect<Equal<Trunc<1.234>, '1'>>,
Expect<Equal<Trunc<12.345>, '12'>>,
Expect<Equal<Trunc<-5.1>, '-5'>>,
Expect<Equal<Trunc<'.3'>, '0'>>,
Expect<Equal<Trunc<'1.234'>, '1'>>,
Expect<Equal<Trunc<'-.3'>, '-0'>>,
Expect<Equal<Trunc<'-10.234'>, '-10'>>,
Expect<Equal<Trunc<10>, '10'>>,
];
类型实现优化与边界情况处理
处理特殊数值
TypeScript中某些特殊数字转换为字符串会有特殊格式,需要额外处理:
// 处理NaN和Infinity
type SafeTrunc<T> =
T extends 'NaN' | 'Infinity' | '-Infinity'
? '0'
: Trunc<T>;
性能优化
对于超长数字字符串,原始实现可能会有性能问题,可以添加长度限制:
type Trunc<T> =
NumberToString<T> extends infer Str
? Str extends string
? Str['length'] extends infer Len
? Len extends 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10
? // 短字符串直接处理
Implementation<Str>
: // 长字符串返回'0'避免性能问题
'0'
: never
: never
: never;
实际应用场景
1. 表单验证
在处理用户输入的数字时,可以使用Trunc类型确保只获取整数部分:
type FormValues = {
age: Trunc<typeof userInput.age>;
quantity: Trunc<typeof userInput.quantity>;
};
2. API响应处理
在处理后端返回的可能包含小数的数值时:
type ApiResponse = {
id: string;
score: Trunc<number>; // 确保是整数部分
};
3. 类型安全的数值计算
结合其他类型工具实现类型安全的数值操作:
type Add Integers<A, B> =
// 使用Trunc确保A和B都是整数
Compute<Trunc<A> + Trunc<B>>;
相关类型挑战对比
| 挑战名称 | 难度 | 核心技术点 | 应用场景 |
|---|---|---|---|
| Trunc | Medium | 模板字面量、类型推断 | 数值处理 |
| Absolute | Medium | 条件类型、符号处理 | 绝对值计算 |
| NumberRange | Medium | 递归类型、联合类型 | 数值范围生成 |
| Sum | Extreme | 元组长度、递归累加 | 数值求和 |
总结与进阶
通过实现Trunc类型工具,我们掌握了TypeScript类型系统中的字符串处理核心技巧。这个看似简单的功能涉及了模板字面量类型的高级应用、条件类型的嵌套使用以及类型推断的精确控制。
知识扩展路线图
后续学习建议
- 尝试实现
Floor、Ceil类型工具处理四舍五入 - 探索
BigInt类型的高级处理技巧 - 研究类型系统中的递归优化策略
掌握这些技巧后,你将能够处理更复杂的类型挑战,编写出更健壮的类型安全代码。记住,类型体操的核心不是炫技,而是培养对TypeScript类型系统的深刻理解,从而在实际项目中编写出更可靠、更易维护的代码。
如果你觉得这篇文章有帮助,请点赞、收藏并关注,下期我们将深入探讨"类型级别实现斐波那契数列"的奥秘!
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



