- 示例工程
【免费下载链接】type-challenges
Collection of TypeScript type challenges with online judge
本篇文章围绕 type-challenges(TypeScript 类型挑战)第 106 题「去除左侧空白 / Trim Left」展开,完整讲解TrimLeft<T>类型工具的实现思路、测试边界与相关扩展。读者读完本文将掌握:如何用递归模板字面量类型在类型层面剥离字符串开头的空白字符,如何通过@type-challenges/utils的Equal断言验证结果,以及如何从本题出发组合出Trim(去除两端空白)与TrimRight(去除右侧空白)等变体。
一、题目要求
题目位于仓库的 questions/00106-medium-trimleft 目录,难度为medium(中等),标签为template-literal(模板字面量类型),由 Anthony Fu(@antfu:
difficulty: medium title: Trim Left tags: template-literal author: github: antfu name: Anthony Fu related: 108, 4803题目描述(见 README.zh-CN.md):
实现
TrimLeft<T>,它接收确定的字符串类型并返回一个新的字符串,其中新返回的字符串删除了原字符串开头的空白字符串。
题目给出的示例:
type trimmed = TrimLeft<' Hello World '> // 应推导出 'Hello World '注意示例细节:只删除开头的空白,'Hello World'结尾的两个空格原样保留,这正是TrimLeft与Trim的区别所在。
二、从零实现:递归模板字面量类型
题目给出的起始模板只有一行占位实现(见 template.ts):
type TrimLeft<S extends string> = any我们需要把any替换为真正的实现。核心思路是条件类型 + 模板字面量类型的模式匹配 + 递归:
- 用模板字面量
'${Whitespace}${Rest}'匹配「一个空白字符 + 其余部分」; - 匹配成功说明开头仍是空白,递归处理
Rest,继续剥离下一个空白字符; - 匹配失败说明开头已经不是空白(或是空字符串),直接返回
S。
完整的参考实现如下:
type Whitespace = ' ' | '\n' | '\t' type TrimLeft<S extends string> = S extends `${Whitespace}${infer Rest}` ? TrimLeft<Rest> : S验证题目示例:
type trimmed = TrimLeft<' Hello World '> // ' Hello World ' 去掉第一个空格 → ' Hello World ' // 再去掉一个空格 → 'Hello World ' // 'H' 不是空白 → 返回 'Hello World ' // 结果:'Hello World '三、空白字符的定义:空格、换行与制表符
观察测试用例(见 test-cases.ts),题目要求的「空白」并不只指普通空格' ',还包括换行符'\n'和制表符'\t':
import type { Equal, Expect } from '@type-challenges/utils' type cases = [ Expect<Equal<TrimLeft<'str'>, 'str'>>, Expect<Equal<TrimLeft<' str'>, 'str'>>, Expect<Equal<TrimLeft<' str'>, 'str'>>, Expect<Equal<TrimLeft<' str '>, 'str '>>, Expect<Equal<TrimLeft<' \n\t foo bar '>, 'foo bar '>>, Expect<Equal<TrimLeft<''>, ''>>, Expect<Equal<TrimLeft<' \n\t'>, ''>>, ]逐条解读这些断言:
| 输入 | 期望输出 | 说明 |
|---|---|---|
'str' | 'str' | 开头无空白,原样返回 |
' str' | 'str' | 去掉 1 个空格 |
' str' | 'str' | 连续 5 个空格全部剥离 |
' str ' | 'str ' | 只去掉左侧 5 个空格,右侧空格保留 |
' \n\t foo bar ' | 'foo bar ' | 空格、换行、制表符混合,全部剥离 |
'' | '' | 空字符串直接返回,递归终止 |
' \n\t' | '' | 全由空白组成,递归剥完,返回空字符串 |
正是第 5 条用例的存在,决定了Whitespace联合类型必须包含' ' | '\n' | '\t'三种字符;如果只处理空格' ','\n'与'\t'无法匹配${Whitespace}${infer Rest},实现就会在混合空白输入上失败。
四、递归终止与边界情况
TrimLeft的递归天然具备终止条件,不需要额外的显式判断:
- 空字符串:
''无法匹配`${Whitespace}${infer Rest}`模板(模板要求至少一个空白字符 + 至少一个后续字符……实际上仅要求存在空白前缀即可,空串显然不满足),走false分支返回''; - 开头非空白:如
'str','s'不属于Whitespace联合,同样走false分支返回原字符串; - 全空白字符串:如
' \n\t',每次剥离一个空白字符后递归,直到剩下的空串无法继续匹配,逐层返回''。
可以验证这一行为与 JavaScript 运行时方法的一致性:' \n\t'.trimStart()返回'',' \n\t foo bar '.trimStart()返回'foo bar '。TrimLeft正是String.prototype.trimStart()(以及trimLeft())在类型层面的等价物。
五、由 TrimLeft 组合出 Trim 与 TrimRight
info.yml中related: 108, 4803明确指出本题与另外两道中等题构成系列关系,它们在仓库中的位置如下:
- 108 去除两端空白字符
Trim:questions/00108-medium-trim/README.zh-CN.md,要求删除字符串两端的空白; - 4803 去除右侧空白
TrimRight:questions/04803-medium-trim-right/README.zh-CN.md,要求只删除结尾的空白。
TrimRight的实现与TrimLeft完全对称,只是把模式匹配放到字符串尾部(对应 test-cases.ts 中的'str '、' foo bar \n\t '等用例):
type Whitespace = ' ' | '\n' | '\t' type TrimRight<S extends string> = S extends `${infer Rest}${Whitespace}` ? TrimRight<Rest> : S而Trim既可以写成「先TrimLeft再TrimRight」的左右剥离组合,也可以一次完成两端剥离。组合式实现最简洁、复用性最好(对应 questions/00108-medium-trim/test-cases.ts 中' str '→'str'的完整剥离断言):
type Trim<S extends string> = TrimRight<TrimLeft<S>>三题共用同一组Whitespace空白字符集合(' ' | '\n' | '\t'),构成了 type-challenges 中「模板字面量类型递归」这一主题的完整练习链:先做TrimLeft掌握左侧剥离与模式匹配,再做TrimRight掌握右侧对称写法,最后用Trim串联两个方向。此外,本仓库还有一批同样依赖模板字面量递归的同类挑战可供继续深入,例如 去除两端空白 Trim 之外的字符串处理题(如 Trim、TrimRight 系列,以及仓库中大量基于字符串拆解递归的 medium/hard 题目)。
六、总结
TrimLeft是理解 TypeScript 模板字面量类型与递归类型的关键入门题。它的要点可以归纳为三点:
- 模式匹配:利用
`${Whitespace}${infer Rest}`在类型层面拆出「首个空白字符 + 剩余部分」,这是所有字符串处理类型工具的基础动作; - 递归剥离:匹配成功时递归调用自身,逐个吃掉开头的空白,直到命中终止条件;
- 空白集合:空白必须定义为
' ' | '\n' | '\t'的联合类型,否则无法通过换行、制表符混合的测试用例。
掌握TrimLeft之后,顺着 related: 108, 4803 继续挑战Trim与TrimRight,即可完整掌握「左右端空白剥离」这一模板字面量类型的经典主题。
- 示例工程
【免费下载链接】type-challenges
Collection of TypeScript type challenges with online judge
相关推荐
深入理解 `unicorn/no-thenable`:禁止 `then` 属性,规避 Promise 与动态导入的隐蔽陷阱
深入理解 unicorn/no thenable :禁止 then 属性,规避 Promise 与动态导入的隐蔽陷阱 核心导读:本文以 eslint plugi
示例工程TypeScript 类型挑战 106:用模板字面量类型实现 TrimLeft 去除字符串左侧空白
TypeScript 类型挑战 106:用模板字面量类型实现 TrimLeft 去除字符串左侧空白 本篇文章围绕 type challenges 仓库中的第 1
示例工程type-challenges 中等题精讲:用模板字面量类型实现 TrimLeft,在类型层面剔除字符串开头的空白
type challenges 中等题精讲:用模板字面量类型实现 TrimLeft,在类型层面剔除字符串开头的空白 导读 本篇围绕 type challenge
示例工程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考