FumadocsZDecode
Typescript

类型系统层级

TypeScript 的类型系统是一个有序的层级结构(类型格,Lattice)。每个类型都有它在层级中的位置——父类型可以接收子类型的值,但反过来不行。理解这个层级,能帮你准确选择类型、排查赋值报错,以及写出更安全的泛型约束。

         any
          |
       unknown
          |
        Object         ← 所有非 null/undefined 值的抽象基类
       /      \
  object     string ── 'hello' ── 字面量类型
    |         |
   {}        number ── 42
    |         |
  null      boolean ── true / false
    |
undefined
    |
  never

层级越靠上,表示的范围越宽。any 在最顶部,可以容纳任意值;never 在最底部,不能容纳任何值。


第一层:any

any 是类型系统的逃逸口,它禁用该变量上的所有类型检查。

赋值方向是双向的——任何值可以赋给 anyany 也可以赋给任何类型:

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

unknownany 的安全替代。它同样能接收任何值,但在使用之前必须先做类型收窄或类型断言。

赋值只允许单向——任何值可以赋给 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。它几乎可以接收任何值,除了 nullundefined

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(小写)代表非原始类型,即排除 stringnumberbooleanbigintsymbolnullundefined 之后的所有类型。

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整数和浮点数423.14NaN
boolean真/假truefalse
bigint任意精度整数9007199254740992n
symbol唯一标识符Symbol('id')
null显式的空值null
undefined未赋值undefined

原始类型之间没有继承关系——string 不是 number 的子类型。它们都是 Object / {} 的子类型(nullundefined 除外)。

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
}

特殊成员:nullundefined

nullundefined 是两个独立的类型,各自只有一个值。它们在类型层级中的位置取决于编译器配置。

开启 strictNullChecks(推荐):

nullundefined 是独立的类型,不能赋给其他类型:

// tsconfig.json: { "strict": true }

const s: string = null      // ❌
const n: number = undefined // ❌

// 必须显式声明可以为 null
const name: string | null = null  // ✅

关闭 strictNullChecks(不推荐):

nullundefined 是所有类型的子类型,可以赋给任意类型——这会隐藏大量空值错误。

// tsconfig.json: { "strict": false }(不推荐)
const s: string = null // ✅ 编译通过,但运行时访问 s.length 会崩溃

区分 nullundefined

// 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,让 nullundefined 受类型保护
  • never 做穷举检查,在编译期捕获遗漏的分支

On this page