DrillLab
第 105 / 105 道105 / 105 · DrillLab 自出By DrillLab

unknown、any、never 各自是什么语义

What do unknown, any and never each mean

先自己答,再往下看Answer it yourself first

一句话:unknown 是 「还不知道是什么,用之前必须先证明」;any 是 「放弃检查,双向放行」;never 是「不可能有值」。 三个词各占类型系统的一个极端。

放进类型层级看:

  • unknown 是顶类型(top type):任何值都能赋给它; 但不先收窄,它什么都做不了 —— 连u.toUpperCase() 都编译不过。
  • never 是底类型(bottom type):它能赋给任何类型, 但没有类型能赋给它 —— 因为它根本没有值。
  • any 不在层级里,它是关掉检查的开关: 双向都能赋,而且会传染 —— 碰过 any 的表达式结果还是 any, 一处 any 能顺着数据流污染一整个模块。

catch (e) 怎么处理:strict 模式下 (TS 4.4 起的 useUnknownInCatchVariables) e 是 unknown,因为 JS 允许 throw 任何值。直接读e.message 编译不过;标准写法是e instanceof Error 收窄后读 message, else 分支 String(e) 兜底。推而广之: JSON.parse 的结果、API 响应、一切外部输入, 入口处都该标 unknown,收窄之后再进业务代码 —— unknown 是边界上的类型。

会追问:「void 和 never 什么区别?」—— void 是正常返回、只是不带值;never 是根本不会正常返回 (throw 或死循环)。「never 还有什么用?」—— 上一题的穷尽检查,加上在 Exclude 里当删除用: never 是空联合,并进联合就消失。 「为什么宁用 unknown 不用 any?」—— unknown 把「先检查再用」变成编译器强制的动作; any 把它变成自觉,而自觉在赶工期的时候最先消失。

In one line: unknown means “not known yet — prove it before you use it”; any means “checking abandoned, both directions waved through”; never means “no value can exist here”. Three words, three extremes of the type system.

Placed in the type hierarchy:

  • unknown is the top type: every value is assignable to it; but until you narrow it, it can do nothing — even u.toUpperCase() refuses to compile.
  • never is the bottom type: it is assignable to everything, yet nothing is assignable to it — because it has no values at all.
  • any sits outside the hierarchy; it is the switch that turns checking off: assignable both ways, and contagious — an expression that touches any becomes any, and one any can pollute a whole module along the data flow.

Handling catch (e): under strict mode (useUnknownInCatchVariables, since TS 4.4) e is unknown, because JS lets you throw anything. Reading e.message directly does not compile; the standard shape is narrowing with e instanceof Error before touching message, with String(e) as the else fallback. Generalize it: the result of JSON.parse, API responses, all external input should enter as unknown and get narrowed before reaching business code — unknown is the type for boundaries.

Follow-up: “void versus never?” — void returns normally, just without a value; never does not return normally at all (it throws, or loops forever). “What else is never for?” — the exhaustiveness check from the previous card, plus playing deletion inside Exclude: never is the empty union, so merging it in makes members disappear. “Why unknown over any?” — unknown turns check-before-use into something the compiler enforces; any turns it into self-discipline, and self-discipline is the first thing to go when a deadline lands.

TypeScript三个极端示意Illustrative
1// any:双向放行 —— 什么都能赋给它,它也能赋给任何类型
2const a: any = JSON.parse('"hi"');
3const n1: number = a; // 编译器不吭声,n1 实际是个字符串
4a.toFixed(); // 编译通过,运行时 TypeError
5
6// unknown:进来随便,出去必须先收窄
7const u: unknown = JSON.parse('"hi"');
8// const n2: number = u; // ✗ Type 'unknown' is not assignable to type 'number'
9// u.toUpperCase(); // ✗ 'u' is of type 'unknown'
10if (typeof u === "string") {
11 u.toUpperCase(); // ✓ 证明它是 string 之后才能用
12}
13
14// never:不可能有值 —— 要么抛错,要么根本走不到
15function fail(msg: string): never {
16 throw new Error(msg);
17}
1// any lets everything through both ways: anything goes into it, and it goes into anything
2const a: any = JSON.parse('"hi"');
3const n1: number = a; // the compiler says nothing, and n1 is really a string
4a.toFixed(); // compiles, then throws a TypeError at runtime
5
6// unknown: anything comes in, but you must narrow it before it goes out
7const u: unknown = JSON.parse('"hi"');
8// const n2: number = u; // ✗ Type 'unknown' is not assignable to type 'number'
9// u.toUpperCase(); // ✗ 'u' is of type 'unknown'
10if (typeof u === "string") {
11 u.toUpperCase(); // ✓ usable once it is proved to be a string
12}
13
14// never: no value is possible — it either throws or is unreachable
15function fail(msg: string): never {
16 throw new Error(msg);
17}
TypeScriptcatch (e) 的标准处理示意Illustrative
1try {
2 JSON.parse("{oops");
3} catch (e) {
4 // strict 下(useUnknownInCatchVariables)e 是 unknown:
5 // console.error(e.message); // ✗ 'e' is of type 'unknown'
6 if (e instanceof Error) {
7 console.error(e.message); // ✓ 收窄成 Error 之后才能读 message
8 } else {
9 console.error(String(e)); // 兜底:JS 允许 throw 任何值,包括字符串
10 }
11}
1try {
2 JSON.parse("{oops");
3} catch (e) {
4 // Under strict (useUnknownInCatchVariables) e is unknown:
5 // console.error(e.message); // ✗ 'e' is of type 'unknown'
6 if (e instanceof Error) {
7 console.error(e.message); // ✓ readable only after narrowing to Error
8 } else {
9 console.error(String(e)); // fallback: JS allows throwing any value, strings included
10 }
11}