TypeScript never 类型详解

TypeScript never 类型详解

什么是 never 类型?

never 是 TypeScript 中的一种特殊类型,表示永远不会发生的值的类型。它通常用于以下场景:

  • 函数永远不会正常返回(抛出错误或无限循环)
  • 类型系统中表示不可能的情况
  • 类型保护中的详尽性检查

基本语法和用法

1. 永远不会返回的函数

typescript 复制代码
// 抛出错误的函数
function throwError(message: string): never {
  throw new Error(message);
}

// 无限循环的函数
function infiniteLoop(): never {
  while (true) {
    console.log('Looping forever...');
  }
}

2. never 的类型检测

typescript 复制代码
type IsNever<T> = [T] extends [never] ? true : false;

type Test1 = IsNever<never>;        // true
type Test2 = IsNever<string>;       // false
type Test3 = IsNever<number | never>; // false

never 在类型系统中的行为

1. 在联合类型中

never 在联合类型中会被自动忽略:

typescript 复制代码
type Union1 = string | never;           // string
type Union2 = number | never | boolean; // number | boolean
type Union3 = never | never;            // never

2. 在交叉类型中

不可能的类型组合会产生 never

typescript 复制代码
type Intersection1 = string & number;   // never
type Intersection2 = { a: number } & { a: string }; // never

3. 数组中的 never

typescript 复制代码
type EmptyArray = never[];  // 空数组类型
const arr: EmptyArray = []; // 只能是空数组

实际应用场景

1. 详尽性检查(Exhaustiveness Checking)

这是 never 类型最重要的应用之一:

typescript 复制代码
type Shape =
  | { kind: 'circle'; radius: number }
  | { kind: 'square'; sideLength: number }
  | { kind: 'triangle'; base: number; height: number };

function getArea(shape: Shape): number {
  switch (shape.kind) {
    case 'circle':
      return Math.PI * shape.radius ** 2;
    case 'square':
      return shape.sideLength ** 2;
    case 'triangle':
      return (shape.base * shape.height) / 2;
    default:
      // 如果 Shape 类型添加了新成员但这里没处理,TypeScript 会报错
      const _exhaustiveCheck: never = shape;
      return _exhaustiveCheck;
  }
}

2. 类型过滤

使用条件类型从联合类型中过滤掉某些类型:

typescript 复制代码
// 过滤掉 null 和 undefined
type NonNullable<T> = T extends null | undefined ? never : T;
type Result = NonNullable<string | number | null | undefined>; // string | number

// 提取字符串类型
type ExtractString<T> = T extends string ? T : never;
type OnlyStrings = ExtractString<string | number | boolean>; // string

// 排除特定类型
type Diff<T, U> = T extends U ? never : T;
type Filtered = Diff<'a' | 'b' | 'c', 'b'>; // 'a' | 'c'

3. 属性排除

从对象类型中排除特定属性:

typescript 复制代码
type RemoveKindField<T> = {
  [K in keyof T as K extends 'kind' ? never : K]: T[K];
};

type Circle = { kind: 'circle'; radius: number };
type WithoutKind = RemoveKindField<Circle>; // { radius: number }

4. 类型保护

typescript 复制代码
function assertNever(x: never): never {
  throw new Error(`Unexpected object: ${x}`);
}

function processValue(value: string | number) {
  if (typeof value === 'string') {
    console.log(value.toUpperCase());
  } else if (typeof value === 'number') {
    console.log(value.toFixed(2));
  } else {
    // 这里的 value 类型应该是 never
    assertNever(value);
  }
}

never 与 void 的区别

理解 nevervoid 的区别非常重要:

特性 void never
返回值 函数正常结束,返回 undefined 函数永远不会正常结束
使用场景 函数没有返回值 抛出错误、无限循环
类型兼容性 可以赋值给 void 不能赋值给其他类型
联合类型 保留在联合类型中 从联合类型中移除
typescript 复制代码
function returnsVoid(): void {
  console.log('返回 void');
  // 隐式返回 undefined
}

function returnsNever(): never {
  throw new Error('永远不会返回');
  // 函数永远不会正常结束
}

// void 可以赋值给 void
let v: void = returnsVoid();

// never 不能赋值给其他类型
// let n: string = returnsNever(); // 错误!

高级用法

1. 递归类型中的 never

typescript 复制代码
// 深度只读类型
type DeepReadonly<T> = {
  readonly [P in keyof T]: T[P] extends object
    ? T[P] extends Function
      ? T[P]
      : DeepReadonly<T[P]>
    : T[P];
};

2. 函数参数中的 never

typescript 复制代码
type FunctionWithNever = (arg: never) => void;
// 这个函数无法被调用,因为没有值可以赋给 never 类型

3. 条件类型中的分布式特性

typescript 复制代码
// 当 T 是联合类型时,条件类型会分布到每个成员上
type ToArray<T> = T extends any ? T[] : never;
type StringArray = ToArray<string | number>; // string[] | number[]

常见误区

1. 不要混淆 never 和 void

typescript 复制代码
// 错误:这个函数实际上会返回 undefined
function wrongNever(): never {
  console.log('This is wrong');
  // 这里应该抛出错误或无限循环
}

// 正确:使用 void
function correctVoid(): void {
  console.log('This is correct');
}

2. 注意 never 在条件类型中的行为

typescript 复制代码
// 注意:never 在条件类型中可能会产生意外的结果
type Test<T> = T extends never ? true : false;
type Result1 = Test<never>; // never (!)
type Result2 = Test<string>; // false

// 正确的写法:使用元组包装
type CorrectTest<T> = [T] extends [never] ? true : false;
type CorrectResult1 = CorrectTest<never>; // true
type CorrectResult2 = CorrectTest<string>; // false

最佳实践

  1. 使用 never 进行详尽性检查:在 switch 语句的 default 分支中使用 never 确保所有情况都被处理
  2. 类型过滤时使用 never:在条件类型中使用 never 来过滤掉不需要的类型
  3. 避免误用:不要在不应该使用 never 的地方使用它
  4. 理解类型系统:深入理解 never 在类型系统中的行为

总结

never 类型是 TypeScript 类型系统中一个强大但容易被误解的特性。正确理解和使用 never 可以帮助你:

  • 编写更安全的代码
  • 进行编译时的详尽性检查
  • 创建更精确的类型定义
  • 避免运行时错误

通过掌握 never 类型,你可以更好地利用 TypeScript 的类型系统来编写健壮、可维护的代码。

相关推荐
Strawberry_rabbit3 小时前
路由配置中的svg图标如何匹配
前端·css
练习前端两年半3 小时前
🔍 你真的会二分查找吗?
前端·javascript·算法
用户52980797824983 小时前
Vue 为何自动加载 index.vue?
前端
北风GI3 小时前
element-plus 自定义主题 最佳实践
前端
CodeCraft Studio3 小时前
国产化PDF处理控件Spire.PDF教程:C#中轻松修改 PDF 文档内容
前端·pdf·c#·.net·spire.pdf·编辑pdf·修改pdf
晴殇i3 小时前
告别 localStorage!探索前端存储新王者 IndexedDB
前端·javascript·面试
Mintopia3 小时前
Next.js 的分布式基础思想:从 CAP 到事件风暴,一路向“可扩展”的银河系巡航
前端·javascript
Moment3 小时前
Next.js 16 Beta:性能、架构与开发体验全面升级 💯💯💯
前端·javascript·github
FIN66683 小时前
昂瑞微:引领射频前端国产化浪潮,铸就5G时代核心竞争力
前端·人工智能·科技·5g·芯片·卫星