类型系统层级
TypeScript 的类型系统是一个有序的层级结构(类型格,Lattice)。每个类型都有它在层级中的位置——父类型可以接收子类型的值,但反过来不行。理解这个层级,能帮你准确选择类型、排查赋值报错,以及写出更安全的泛型约束。
any
|
unknown
|
Object ← 所有非 null/undefined 值的抽象基类
/ \
object string ── 'hello' ── 字面量类型
| |
{} number ── 42
| |
null boolean ── true / false
|
undefined
|
never层级越靠上,表示的范围越宽。any 在最顶部,可以容纳任意值;never 在最底部,不能容纳任何值。
第一层:any
any 是类型系统的逃逸口,它禁用该变量上的所有类型检查。
赋值方向是双向的——任何值可以赋给 any,any 也可以赋给任何类型:
let a: any
// 任何值都可以赋给 any
a = 123
a = 'hello'
a = { x: 1 }
a = null
// any 可以赋给任何类型(TypeScript 不报错)
const str: string = a // ✅
const num: number = a // ✅any 变量上的任何属性访问、方法调用都不会被检查:
const a: any = 42
a.toFixed(2) // ✅ TypeScript 不检查 number 上有没有这个方法
a.foo.bar.baz // ✅ 链式访问也不报错何时使用 any:
- 迁移 JavaScript 旧代码到 TypeScript 的过渡期
- 第三方库没有类型声明时的临时替代
- 类型推导极其复杂、确实无法标注时
尽量不在正式代码中用 any。它让 TypeScript 退化成 JavaScript,类型错误只会在运行时才暴露。
第二层:unknown
unknown 是 any 的安全替代。它同样能接收任何值,但在使用之前必须先做类型收窄或类型断言。
赋值只允许单向——任何值可以赋给 unknown,但 unknown 不能直接赋给其他类型:
let a: unknown
// 任何值都可以赋给 unknown
a = 123
a = 'hello'
a = { x: 1 }
// unknown 不能直接赋给其他类型 ❌
const str: string = a // ❌ Type 'unknown' is not assignable to type 'string'使用 unknown 值之前,必须先告诉 TypeScript 它的真实类型:
const data: unknown = fetchData()
// 方式一:类型断言(你确定类型时使用)
const name = (data as { name: string }).name
// 方式二:类型守卫(运行时安全)
if (typeof data === 'string') {
console.log(data.toUpperCase()) // ✅ 这里 TypeScript 知道 data 是 string
}
// 方式三:instanceof 检查
if (data instanceof Error) {
console.log(data.message) // ✅
}何时使用 unknown:
- 接收来自外部的数据(API 响应、用户输入、
JSON.parse结果) - 捕获的异常(
catch (e: unknown)) - 需要表达"任意类型"但不想放弃类型安全时
unknown 是取代 any 的首选方案。它强制你在使用前验证类型,把潜在的运行时错误变成编译期错误。
第三层:Object(大写 O)
Object(首字母大写)是 JavaScript 中所有对象的基类,对应 Object.prototype。它几乎可以接收任何值,除了 null 和 undefined。
let o: Object
o = 42 // ✅ number 有 Object.prototype 方法
o = 'hello' // ✅
o = true // ✅
o = {} // ✅
o = [] // ✅
o = null // ❌ null 不在这里
o = undefined // ❌ undefined 也不在这里Object 提供的接口只有 Object.prototype 上的方法:toString()、hasOwnProperty() 等。
const o: Object = 42
o.toString() // ✅
o.hasOwnProperty('x') // ✅
;(o as number).toFixed(2) // 需要断言才能访问 number 专有方法实践中,Object(大写)几乎不直接使用。用 object(小写)或具体类型替代它。
第四层:object(小写 o)
object(小写)代表非原始类型,即排除 string、number、boolean、bigint、symbol、null、undefined 之后的所有类型。
let o: object
o = {} // ✅ 普通对象
o = [] // ✅ 数组也是对象
o = () => {} // ✅ 函数也是对象
o = new Date() // ✅
o = 42 // ❌ number 是原始类型
o = 'hello' // ❌ string 是原始类型
o = true // ❌ boolean 是原始类型
o = null // ❌object 类型的变量不能直接访问任何属性,因为 TypeScript 不知道它的具体结构:
const o: object = { name: 'Alice' }
o.name // ❌ Property 'name' does not exist on type 'object'实践建议: 用 Record<string, unknown> 或具体接口替代 object,这样可以安全访问属性。
第五层:{}(空对象类型)
{} 类型表示任何非 null 且非 undefined 的值。它和 Object(大写)行为几乎相同,但来自类型系统层面而不是 JavaScript 原型链。
let a: {}
a = 42 // ✅
a = 'hello' // ✅
a = true // ✅
a = {} // ✅
a = [] // ✅
a = null // ❌
a = undefined // ❌{} 不代表"空的对象",它代表的是"有 Object.prototype 方法的值"。
const a: {} = 42
a.toString() // ✅ 但不能访问 number 专有方法实践建议: 用 {} 作为泛型约束时要小心——它比你想象的范围宽得多。用 Record<string, unknown> 表示对象,用 NonNullable<T> 排除 null/undefined。
第六层:原始类型
TypeScript 的原始类型直接对应 JavaScript 的七种原始值类型:
| 类型 | 对应值 | 示例 |
|---|---|---|
string | 文本 | 'hello'、"world" |
number | 整数和浮点数 | 42、3.14、NaN |
boolean | 真/假 | true、false |
bigint | 任意精度整数 | 9007199254740992n |
symbol | 唯一标识符 | Symbol('id') |
null | 显式的空值 | null |
undefined | 未赋值 | undefined |
原始类型之间没有继承关系——string 不是 number 的子类型。它们都是 Object / {} 的子类型(null 和 undefined 除外)。
const s: string = 'hello'
const n: number = 42
const b: boolean = true
const big: bigint = 100n
const sym: symbol = Symbol('id')string 的层级细节
// string 可以接收字面量类型
const s: string = 'hello' // ✅ 'hello' 是 string 的子类型
const s2: string = `world` // ✅ 模板字符串也是 string
// 但反过来不行
const literal: 'hello' = s // ❌ string 不能赋给字面量类型第七层:字面量类型
字面量类型是原始类型的更精确版本,表示一个具体的值。
// 字符串字面量
const direction: 'left' | 'right' = 'left'
// 数字字面量
const statusCode: 200 | 404 | 500 = 200
// 布尔字面量(boolean 本身就是 true | false 的联合)
const flag: true = true
// 模板字面量类型
type EventName = `on${string}` // 匹配 'onClick', 'onChange' 等
const handler: EventName = 'onClick' // ✅字面量类型是对应原始类型的子类型:
// 'hello' 是 string 的子类型,可以赋给 string
const s: string = 'hello' as 'hello' // ✅
// 42 是 number 的子类型
const n: number = 42 as 42 // ✅字面量类型常用于联合类型和判别联合(Discriminated Union):
type Shape =
| { kind: 'circle'; radius: number }
| { kind: 'rect'; width: number; height: number }
function area(shape: Shape): number {
if (shape.kind === 'circle') {
return Math.PI * shape.radius ** 2 // TypeScript 知道这里是 circle
}
return shape.width * shape.height // TypeScript 知道这里是 rect
}特殊成员:null 和 undefined
null 和 undefined 是两个独立的类型,各自只有一个值。它们在类型层级中的位置取决于编译器配置。
开启 strictNullChecks(推荐):
null 和 undefined 是独立的类型,不能赋给其他类型:
// tsconfig.json: { "strict": true }
const s: string = null // ❌
const n: number = undefined // ❌
// 必须显式声明可以为 null
const name: string | null = null // ✅关闭 strictNullChecks(不推荐):
null 和 undefined 是所有类型的子类型,可以赋给任意类型——这会隐藏大量空值错误。
// tsconfig.json: { "strict": false }(不推荐)
const s: string = null // ✅ 编译通过,但运行时访问 s.length 会崩溃区分 null 和 undefined:
// null 表示"显式的空"——你主动设置了它
let user: User | null = null
// undefined 表示"尚未赋值"——变量存在但没有值
let count: number | undefined可选链和空值合并:
const name = user?.profile?.name ?? '匿名'
// user 为 null 或 undefined 时,name 得到 '匿名'底层类型:never
never 是类型层级的底部,表示永远不会发生的类型。它是所有类型的子类型,但没有任何值是 never 类型。
never 出现在三种场景:
1. 永远不会返回的函数
function throwError(msg: string): never {
throw new Error(msg) // 函数抛出异常,永远不会正常返回
}
function infiniteLoop(): never {
while (true) {} // 无限循环,永远不会结束
}2. 穷举检查(Exhaustiveness Check)
type Shape = 'circle' | 'rect' | 'triangle'
function describe(shape: Shape): string {
switch (shape) {
case 'circle': return '圆形'
case 'rect': return '矩形'
case 'triangle': return '三角形'
default: {
// 如果 Shape 新增了一个成员但这里没有处理
// TypeScript 会报错:不能把新类型赋给 never
const _exhaustive: never = shape
return _exhaustive
}
}
}3. 条件类型收窄后的空集
type OnlyString<T> = T extends string ? T : never
type A = OnlyString<string | number | boolean>
// A = string(number 和 boolean 被过滤为 never,never 在联合中被丢弃)never 的关键性质:
// never 是所有类型的子类型,可以赋给任何类型
function fail(): never { throw new Error() }
const s: string = fail() // ✅ TypeScript 允许(虽然运行时不会到这里)
const n: number = fail() // ✅
// 但没有任何值能赋给 never
const x: never = 'hello' // ❌
const y: never = 42 // ❌类型层级总结
| 层级 | 类型 | 可接收的值 | 赋值给其他类型 |
|---|---|---|---|
| 顶层 | any | 任意值 | 任意类型(禁用检查) |
| 顶层 | unknown | 任意值 | 必须先收窄 |
| 广泛 | Object | 除 null/undefined 外的所有值 | 仅更宽的类型 |
| 广泛 | object | 所有非原始值 | 仅更宽的类型 |
| 广泛 | {} | 除 null/undefined 外的所有值 | 仅更宽的类型 |
| 原始 | string / number / boolean / … | 对应类型的值 | 仅更宽的类型 |
| 精确 | 'hello' / 42 / true / … | 单个具体值 | 对应的原始类型 |
| 特殊 | null / undefined | 各自唯一的值 | 取决于 strictNullChecks |
| 底层 | never | 没有值 | 任意类型 |
选型建议:
- 来自外部的数据用
unknown,用完再收窄,不要用any - 表示"任意对象"时用
Record<string, unknown>而不是object或{} - 开启
strict: true,让null和undefined受类型保护 - 用
never做穷举检查,在编译期捕获遗漏的分支