loader

Nerio News Magazine brings you trusted, timely and thought-provoking stories from around the globe.

Follow Us

TypeScript 25 问:从 any/unknown 到类型体操与 TS 7

Share This Article:

25 问覆盖类型基础(any/unknown/never/收窄)、类型编程(映射类型/infer/DeepPartial/satisfies)与工程配置(strict/project references/类型测试)三个梯度,含 TS 7 原生编译器迁移相关的新考点。

TypeScript 25 问:从 any/unknown 到类型体操与 TS 7
关键词TypeScript 面试、泛型、条件类型、infer、类型体操、TS7 原生编译器

TypeScript 面试正在从「会不会用」升级为「类型系统理解多深」——尤其在 TypeScript 7 原生编译器(Go 实现,当前 7.0.2)落地的背景下,语言层知识比以往更值钱。这份 25 问覆盖类型基础、类型编程、工程配置三个梯度,每问给出简明答案与加分点。

一、类型基础(8 问)

1. any 与 unknown 的区别?

any 关闭类型检查,可以赋给任何类型、调用任何方法;unknown 是「安全的 top type」——可以接收任何值,但使用前必须收窄。工程规范:任何想写 any 的地方先考虑 unknown。

2. never 的用途?

表示「不会有值」:抛错函数的返回值、穷尽检查的兜底分支(const _x: never = value 在联合类型扩展时编译报错,强制处理新分支)。

3. type 与 interface 怎么选?

两者大部分场景可互换。差异:interface 可声明合并、可 extends;type 支持联合、条件类型、映射等类型编程。惯例:对象形状用 interface,其余一律 type。

4. 枚举的问题是什么?替代方案?

运行时对象 + 数字枚举的双向映射有陷阱,const enum 在隔离模块编译下受限。主流替代:as const 对象 + 联合类型推导。

const STATUS = { active: "active", archived: "archived" } as const;
type Status = typeof STATUS[keyof typeof STATUS]; // "active" | "archived"

5. 可辨识联合是什么?为什么推荐?

用字面量成员(如 kind: "circle")区分联合分支,switch 自动收窄。它把「合法状态」编码进类型,配合 never 穷尽检查消灭漏分支。

6. 类型收窄有哪些手段?

typeof/instanceof、in 操作符、相等判断、谓词函数(value is T)、断言函数(asserts value is T)。加分点:说明 TS 的窄化基于控制流分析,跨异步边界会失效(回调里需要重新校验)。

7. 泛型的「分配律」陷阱是什么?

条件类型对裸类型参数是分布式求值:T extends U ? X : Y 作用在联合类型时会逐成员计算。想跳过分布用 [T] extends [U]

8. readonly 数组与 const 的区别?

const 锁绑定(引用不可换),readonly 锁内容(元素不可改)。ReadonlyArray<T> / as const 是构建不可变数据流的基础。

any / unknown / never 一图分清

二、类型编程(10 问)

9. Partial/Required/Pick/Omit 的实现原理?

全是映射类型:Partial<T> = { [K in keyof T]?: T[K] };Omit 借助 Exclude:{ [K in Exclude<keyof T, U>]: T[K] }。能手写这四个是类型编程的入场券。

10. infer 怎么用?举例。

在条件类型中「声明一个待推断变量」:type Unwrap<T> = T extends Promise<infer V> ? V : T。常见于提取数组元素、函数返回值(ReturnType 的实现)。

11. 写一个 DeepPartial。

type DeepPartial<T> = T extends object
  ? { [K in keyof T]?: DeepPartial<T[K]> }
  : T;

加分点:注意数组与函数的处理边界(先排除 function 再递归,或对数组映射为 DeepPartial<元素>[])。

12. Exclude 与 Extract 的原理?

基于分布式条件类型:Exclude<T, U> = T extends U ? never : T。联合类型进、联合类型出。

13. 模板字面量类型能做什么?

字符串级的类型运算:路由模式解析(`/user/${infer Id}`)、事件名推导、CSS 单位约束。配合映射类型可实现「字段名改造」(如 getter 前缀)。

14. satisfies 关键字解决什么问题?

「校验但不拓宽」:const config = {...} satisfies Config 既保证符合 Config,又保留每个字面量的精确类型(as 断言会丢失收窄)。TS 4.9 引入,现代代码里应优先于带类型的 const 声明。

15. 协变与逆变体现在哪?

对象属性协变(子类型可以赋给父类型位置),函数参数逆变(参数要求父类型的位置不能传要求子类型的函数)。strictFunctionTypes 开启后参数才严格逆变。

16. 函数重载怎么写?实现签名为什么不暴露?

多个重载签名 + 一个实现签名;实现签名只用于内部兼容,对外不可见,调用方只能匹配重载签名——这保证了调用侧类型的精确性。

17. 声明合并与模块增强的场景?

给第三方库补类型:declare module "lib" { interface Options { debug?: boolean } }。加分点:提醒声明合并是双刃剑,滥用会让类型「来源不可考」。

18. 装饰器与 TS 7 的关系?

TS 7(原生编译器)以 ECMAScript 标准装饰器为准,旧实验性装饰器语义不保证兼容——存量代码迁移 TS 7 时这是重点检查项之一(详见 TypeScript 官方仓库的迁移说明)。

三、工程配置(7 问)

19. strict 模式包含哪些关键项?

strictNullChecks(可空性)、noImplicitAny(隐式 any 报错)、strictFunctionTypes、strictBindCallApply、alwaysStrict 等。面试观点:新项目全开,存量项目从 strictNullChecks 开始逐项开启。

20. skipLibCheck 为什么建议开?

跳过对 node_modules 内 .d.ts 的检查——第三方声明的错误不该阻塞你的构建,且检查它们耗时巨大。自己代码的检查不受影响。

21. isolatedModules 意味着什么?

每个文件必须能被独立转译(单文件视角类型可判定)。禁止跨文件的纯类型副作用,这也是与 esbuild/swc 类单文件编译器兼容的前提。

22. 项目引用(project references)解决什么?

monorepo 增量构建:拆成多个 tsconfig,依赖方只看被依赖方的 .d.ts,改一个包不必全量类型检查。配合 TS 7 原生编译器的并行能力,收益进一步放大。

23. 类型测试怎么写?

用类型断言「可赋值/不可赋值」测试工具类型:expectTypeOf(x).toEqualTypeOf<Expected>()(vitest)或 @ts-expect-error 标注预期的编译错误。类型也是代码,也要回归测试。

24. public_api 与内部类型的边界怎么管?

导出面最小化:包入口只 export 公共 API,内部类型用 /** @internal */ 标记并由 api-extractor 生成报告做破坏性变更检查。

25. 什么情况下「any 一次」是可接受的?

边界处的三方无类型数据(立即收窄)、原型验证期的临时通道——但必须集中、注释原因、限期消灭。这问实际考察的是对「类型系统是渐进的」这一设计哲学的理解。

标签

#TypeScript#前端面试#类型系统#面试题

Related Post

发表回复

Your email address will not be published.