96SEO 2026-04-20 21:32 0
说实话,那场面我现在想起来还觉得有点尴尬。上周去面试一个高级前端岗,前面聊得挺嗨,架构设计、性Neng优化dou对答如流。结果面试官突然笑眯眯地甩过来一道题:“实现一个 DeepReadonly,要求递归把所有嵌套属性变成只读。”

我盯着屏幕愣了大概十秒钟。平时写业务代码,interfacegenericunion type 随手就来感觉自己 TypeScript 用得挺溜。但一碰到这种“类型编程”——社区里戏称为“类型体操”的东西——脑子瞬间就卡壳了。Zui后那个 offer 自然是没了花了一个 offer 的代价去补这块短板,这性价比到底咋样先不说但这技Neng树确实是被迫点上了 🤷♂️。
痛定思痛,我花了一个周末把 type-challenges 仓库里的题刷了一遍,从 Easy 到 Medium,核心套路算是摸清楚了。今天就把我觉得Zui值得刷、面试出现频率Zui高的 5 道题整理出来。我不打算只贴个答案就跑,我会尽量把“为什么这么写”背后的逻辑讲明白。毕竟类型体操说白了就是用 extendsinferin递归Zuo模式匹配。一旦你接受了“TypeScript 的类型系统本质上是一门函数式编程语言”这个设定,hen多写法其实就顺理成章了。
在开战之前,得先搞清楚手里有哪些工具。刷了几十道题你会发现,来来回回其实就是这几个概念在排列组合:
条件类型T extends U ? X : Y,这就是类型世界的 if-else 逻辑判断。
infer 关键字只Neng在条件类型里用,用来“捕获”一个待推断的类型变量,有点像正则里的捕获组。
映射类型{ : ... },用来遍历对象类型的每一个 key,Zuo批量操作。
递归类型Ke以引用自身,这是处理嵌套结构的核心。
模板字面量类型${A}${B},Neng在类型层面操作字符串,简直神技。
元组解构用 把数组拆开。
搞懂这几个,Medium 难度的题基本douNeng推出来。下面直接上干货。
第一题:实现MyPick
这是 type-challenges 的第一道题,也是理解映射类型的敲门砖。虽然简单,但它是后面所有复杂操作的基础。
目标从对象类型 T 中选出指定属性 K,模拟 TS 内置的 Pick。
interface Todo {
title: string
description: string
completed: boolean
}
// 期望结果:{ title: string; completed: boolean }
type TodoPreview = MyPick
思路解析
这题的核心在于“映射”。我们需要遍历 K 里的每一个 key,然后去 T 里把对应的类型找出来。
type MyPick = {
: T
}
这里有两个关键点:
K extends keyof T这是一个约束条件。它的意思是你不Neng Pick 一个 T 上不存在的属性。Ru果没有这个约束,你传个不存在的 key 进来类型系统就报错,这才是类型安全该有的样子。
这就是遍历联合类型 K 的每一个成员。然后 T 就是索引访问,取值。
TrimLeft
第一次kan到模板字面量类型Neng这么用的时候,我是真的震惊了。这相当于在类型层面Zuo字符串正则匹配,TS 的类型系统是图灵完备的,我是真信了。
目标去掉字符串类型左侧的空白字符。
type Space = ' ' | '
' | '\t'
// 期望:'hello '
type Result = TrimLeft<' hello '>
思路解析
这题考的是递归匹配。每次kan字符串的开头是不是空白字符,Ru果是就把它“砍掉”,然后对剩下的部分递归调用;Ru果不是说明左边Yi经干净了直接返回。
type TrimLeft =
S extends `${Space}${infer Rest}`
? TrimLeft
: S
拆解一下逻辑:
S extends `${Space}${infer Rest}`这是模式匹配。Ru果 S 符合“空白字符 + 剩余部分”这种格式,infer Rest 就会捕获那个剩余部分。
TrimLeft递归调用自己,继续处理剩下的字符串。
: SRu果开头不是空白字符了直接返回 S,递归终止。
想要实现完整的 Trim,其实hen简单,组合一下就行:
type TrimRight =
S extends `${infer Rest}${Space}`
? TrimRight
: S
type Trim = TrimLeft
第三题:实现 MyReturnType
面试高频题,主要考 infer 的用法。hen多时候我们需要把函数的返回值提取出来单独用。
目标提取函数类型的返回值类型。
const fn = => {
if return 1
else return 2
}
// 期望:1 | 2
type Result = MyReturnType
思路解析
核心思想是用条件类型 + infer 从函数签名里“抠出”返回值。
type MyReturnType any> =
T extends => infer R ? R : never
这里有个小细节:
T extends => any先约束 T 必须是个函数,不然传个基础类型进来就没意义了。
T extends => infer R
匹配,这次用 infer R 声明一个“待推断”的类型变量 R 放在返回值的位置。
Ru果匹配成功,R 就会被自动推断成具体的返回值类型;否则返回 never。
踩坑infer hen傲娇,只Neng在 extends 的条件子句里用。我第一次写的时候尝试在外面单独声明 infer R,直接报语法错误。记住这个关键字就是跟条件类型绑定的,离开 extends ? : 它啥也不是。
DeepReadonly
就是那个让我挂掉面试的题。搞定之后发现其实不难,核心就是递归,但细节上全是坑。
目标递归地将对象所有属性变为只读。
interface Nested {
a: { b: { c: string }; d: number }
e: boolean
}
// 期望:所有层级的属性dou是 readonly
type Result = DeepReadonly
思路解析
第一反应可Neng是这样写:
type DeepReadonly = {
readonly : T extends Record
? DeepReadonly
: T
}
等一下这个写法有坑。
测试的时候你会发现,数组类型也会被当成对象处理。结果数组变成了一个奇怪的映射类型,把 lengthpush 这些属性全变成 readonly 了这显然不是我们要的 readonly number。而且,函数类型也是 object,不先排除会把函数签名拆掉。
改进版本:
type DeepReadonly = T extends => any
? T // 函数类型原样返回
: T extends object
? { readonly : DeepReadonly }
: T // 原始类型原样返回
这里用 T extends object 替代了 Record 判断,并且优先排除了函数类型。实测下来数组也Neng正确处理——TS 对数组有特殊的映射类型行为,{ readonly ]: ... } 会正确生成 readonly number。
Flatten
数组/元组操作的经典题目,考的是综合运用Neng力。
目标把嵌套数组类型拍平成一维。
type Result = Flatten<, ]]]>
// 期望:]
思路解析
元组解构 + 递归。取第一个元素,Ru果是数组就展开,不是就保留,然后递归处理剩余部分。
type Flatten = T extends
? First extends any
?
:
:
逻辑拆解:
利用模式匹配把元组拆成“第一个元素”和“剩余元素”。
First extends any判断第一个元素是不是数组。
Ru果是数组,递归拍平它,再拼上递归拍平后的 Rest。
Ru果不是数组,直接把 First 放进结果,继续处理 Rest。
遇到空数组 ,返回 ,递归终止。
刷题过程中了几个省时间的技巧,希望Neng帮你少走弯路:
1. TS Playground 是Zui好的练习场有兴趣的Ke以直接去 type-challenges 开刷,仓库里有在线 playground 链接,零配置直接开写。右侧面板开 .D.TS Ke以kan到类型推断结果。直接用 TypeScript Playground 在线调试,不用搭本地环境,非常方便。
@ts-expect-error Zuo断言测试
怎么验证你的类型是对的?写测试用例啊!
type Assert = T
type IsEqual = => T extends A ? 1 : 2) extends => T extends B ? 1 : 2) ? true : false
// 测试你的实现
type Case1 = Assert, { title: string }>
// Ru果类型不对,这行会报错
3. 用空映射强制展开类型
调试类型体操的时候,多用 hover kan推断结果,VS Code 里鼠标悬停在类型别名上就Nengkan到展开后的类型。但有时候 hover kan到的是 DeepReadonly,没展开。加一层空映射Ke以强制 TS 展开显示:
type Expand = T extends infer O ? { : O } : never
// 使用
type Check = Expand
// hover Check 就Nengkan到完全展开的结构
4. 小心递归深度限制
TS 类型递归有深度限制,嵌套太深会报 Type instantiation is excessively deep and possibly infinite。实际业务中极少遇到,刷题偶尔会被卡住。遇到了Ke以试试“尾递归优化”的写法——TS 4.5+ 对尾递归类型有一定优化,但不是所有场景dou生效。
我自己的感受是:类型体操训练的是你对类型系统的直觉,不是用来炫技的。刷过之后再去读开源库的类型定义,比如 Vue 3 的模板类型推断、tRPC 的端到端类型安全,就不再觉得是天书了。
比较合理的刷题路径大概是这样:
先搞懂上面 6 个基础工具,每个写 2-3 个小例子。
type-challenges 的 Easy 全刷,大概 10 几道,一下午Neng搞定。
Medium 挑着刷,优先刷面试高频的:DeepReadonlyFlattenTrimLeftIsUnionTupleToUnion。
Hard kan心情——实际业务中几乎用不到,但对理解类型系统的Neng力边界有帮助。
别被这些符号吓退了一旦你习惯了这种思维方式,你会发现用 TypeScript 写代码其实比写 JavaScript 还要爽。毕竟编译器帮你挡掉了 90% 的低级错误,这种安全感是谁dou不想放弃的。
作为专业的SEO优化服务提供商,我们致力于通过科学、系统的搜索引擎优化策略,帮助企业在百度、Google等搜索引擎中获得更高的排名和流量。我们的服务涵盖网站结构优化、内容优化、技术SEO和链接建设等多个维度。
| 服务项目 | 基础套餐 | 标准套餐 | 高级定制 |
|---|---|---|---|
| 关键词优化数量 | 10-20个核心词 | 30-50个核心词+长尾词 | 80-150个全方位覆盖 |
| 内容优化 | 基础页面优化 | 全站内容优化+每月5篇原创 | 个性化内容策略+每月15篇原创 |
| 技术SEO | 基本技术检查 | 全面技术优化+移动适配 | 深度技术重构+性能优化 |
| 外链建设 | 每月5-10条 | 每月20-30条高质量外链 | 每月50+条多渠道外链 |
| 数据报告 | 月度基础报告 | 双周详细报告+分析 | 每周深度报告+策略调整 |
| 效果保障 | 3-6个月见效 | 2-4个月见效 | 1-3个月快速见效 |
我们的SEO优化服务遵循科学严谨的流程,确保每一步都基于数据分析和行业最佳实践:
全面检测网站技术问题、内容质量、竞争对手情况,制定个性化优化方案。
基于用户搜索意图和商业目标,制定全面的关键词矩阵和布局策略。
解决网站技术问题,优化网站结构,提升页面速度和移动端体验。
创作高质量原创内容,优化现有页面,建立内容更新机制。
获取高质量外部链接,建立品牌在线影响力,提升网站权威度。
持续监控排名、流量和转化数据,根据效果调整优化策略。
基于我们服务的客户数据统计,平均优化效果如下:
我们坚信,真正的SEO优化不仅仅是追求排名,而是通过提供优质内容、优化用户体验、建立网站权威,最终实现可持续的业务增长。我们的目标是与客户建立长期合作关系,共同成长。
Demand feedback