← 返回导航

any / unknown / never 的区别

进阶技巧 · 基础

场景说明

any、unknown、never 是 TypeScript 中三个特殊且容易混淆的类型。它们分别代表"任意类型"、"未知类型"和"永不发生的类型"。理解它们的区别和适用场景,是写出安全 TypeScript 代码的基础。

完整代码


      
// any vs unknown vs never 的完整对比

// ==================== any ====================
// 放弃类型检查,可以赋值给任何变量,也可以访问任何属性
let a: any = 'hello';
a = 42;                    // OK
a = true;                  // OK
a.foo.bar.baz();          // OK(编译通过,运行时崩溃)
const s: string = a;      // OK,any 可以赋值给任何类型
const n: number = a;      // OK

// ==================== unknown ====================
// 类型安全的 any,不能直接使用,必须先收窄
let u: unknown = 'hello';
u = 42;                    // OK
u = true;                  // OK
// u.foo.bar.baz();       // 错误!unknown 不能访问属性
// const s2: string = u;  // 错误!unknown 不能赋值给 string

// 正确用法:先收窄再使用
if (typeof u === 'string') {
  const s3: string = u;  // OK,u 已收窄为 string
  console.log(u.toUpperCase());
}

// ==================== never ====================
// 表示永远不会发生的类型,用于穷尽性检查

// never 是任何类型的子类型,可以赋值给任何类型
const nev: never = (() => { throw new Error('never'); })() as never;
const str: string = nev;   // OK,never 可以赋值给任何类型
const num: number = nev;   // OK

// 没有任何类型可以赋值给 never(除了 never 本身)
// const n2: never = 'hello';  // 错误!

// 实战:穷尽性检查
type Shape =
  | { kind: 'circle'; radius: number }
  | { kind: 'square'; side: number }
  | { kind: 'triangle'; base: number; height: number };

function area(shape: Shape): number {
  switch (shape.kind) {
    case 'circle':
      return Math.PI * shape.radius ** 2;
    case 'square':
      return shape.side ** 2;
    case 'triangle':
      return (shape.base * shape.height) / 2;
    default:
      // 如果 Shape 新增了类型但忘记添加 case,这里会编译报错
      const _exhaustive: never = shape;
      return _exhaustive;
  }
}

关键点解析

使用示例与类型推导


      
// 实战:API 返回 unknown 后类型守卫收窄

interface User {
  id: number;
  name: string;
}

// 模拟 fetch:返回 unknown
async function fetchData(url: string): Promise<unknown> {
  const res = await fetch(url);
  return res.json();
}

function isUser(data: unknown): data is User {
  return (
    typeof data === 'object' &&
    data !== null &&
    'id' in data &&
    'name' in data
  );
}

// 使用
async function getUser() {
  const data = await fetchData('/api/user/1');
  if (isUser(data)) {
    // data 被收窄为 User
    console.log(data.name);
  } else {
    console.error('Invalid user data');
  }
}
在 Playground 中尝试