Yuzhe's Blog

yuzhes

Absolute

Absolute

题目链接

题目

实现 Absolute 类型,接受 stringnumberbigint,输出一个正数字符串。

type Test = -100
type Result = Absolute<Test> // 期望结果:"100"

解答

type Absolute<T extends number | string | bigint> =
  `${T}` extends `-${infer N}` ? N : `${T}`

一行搞定:先转成字符串,再把开头的负号去掉(如果有的话)。

分步解析

第一步 — 用模板字面量统一转成字符串

`${T}`

TypeScript 的模板字面量类型可以直接插值 numberstringbigint,将它们的字面量类型转换为字符串字面量:

type A = `${-100}`    // "-100"
type B = `${100}`     // "100"
type C = `${-42n}`    // "-42"
type D = `${"-100"}`  // "-100"

这一步统一处理了三种输入类型,无需分别讨论。

第二步 — 模式匹配,检测开头的负号

`${T}` extends `-${infer N}` ? N : `${T}`

所有测试用例演算

输入`${T}`匹配 -${N}结果
-100"-100"N = "100""100"
100"100""100"
"-100""-100"N = "100""100"
"100""100""100"
-42n"-42"N = "42""42"
0"0""0"

深入理解

为什么返回值必须是字符串?

你可能会期望 Absolute<-100> 返回数字 100 而不是字符串 "100"。原因在于 TypeScript 类型系统的局限:去掉负号本质上是字符串操作,模板字面量 infer 模式只能返回字符串。如果要把结果再转回数字类型,需要用 tuple 长度技巧,且对大数字会失效 — 得不偿失。

模板字面量:不只是拼接,更是字符串解析工具

这道题展示了模板字面量类型的另一面——不只能拼接字符串,还能解析和提取字符串的一部分:

// 提取路径第一段
type Head<S extends string> = S extends `${infer H}/${string}` ? H : S

// 检测前缀
type StartsWithA<S extends string> = S extends `A${string}` ? true : false

// 去掉开头的负号
type StripMinus<S extends string> = S extends `-${infer R}` ? R : S

模板字面量中的 infer 是关键,可以提取匹配到的任意子串。

约束 T extends number | string | bigint

为什么要包含 bigint?因为 JavaScript 的 BigInt 字面量(如 -42n)在 TypeScript 中是独立的原始类型。不加 bigint 的话,传入 bigint 类型会报错。模板字面量插值三者都支持,所以约束范围与能力一致。

-0 怎么处理?

负零 (-0) 是 JavaScript 的一个特殊值。在 TypeScript 类型系统中,-0 作为数字字面量类型通常被视为 0,所以 Absolute<-0> 得到 "0" — 结果是正确的。

为什么不用数值比较的方式?

你可能想到 T > 0 ? T : -T 这样的写法,但 TypeScript 类型层面的数值运算极为有限,无法直接对任意数字字面量取反。字符串模式匹配是目前最简洁、最通用的解法。

关键要点