首页 / TypeScript 入门教程 / void、null、undefined、never

TypeScript 入门教程

void、null、undefined、never

本教程共 80 篇 · 第 14 篇 · 更新于 2026-08-10 · 约 10 分钟阅读

TypeScriptTypeScript 入门教程voidnullundefinedneverstrictNullChecks

本节目标:理解四个特殊类型的含义和使用场景,掌握 strictNullChecks 对 null/undefined 行为的改变,学会用非空断言和 never 做穷尽检查。

void:函数没有返回值

void 表示”这个函数没有返回值”。一个函数如果只有副作用(打印、写文件、发网络请求)而不返回任何东西,它的返回值类型就写 void

function log(message: string): void {
  console.log(message);
  // 没有 return 语句
}

function handleClick(): void {
  console.log("按钮被点击了");
}

JavaScript 中”没有返回值”的函数实际返回 undefined。所以从运行时的角度看,void 约等于 undefined。但 TypeScript 把 voidundefined 里独立出来——语义上你用它表达”这个函数的返回值没有意义,别去用它”。

function greet(): void {
  console.log("你好");
}

let result = greet();       // result 的类型是 void
// result.toUpperCase();    // ❌ void 类型没有这个方法

把返回值类型写成 undefined 也是可以的,但那意味着你必须写 return undefinedvoid 允许你不写 return。

null 和 undefined

nullundefined 在 JavaScript 里是两个不同的值:null 表示”空值”——开发者主动设置的空,undefined 表示”未定义”——变量声明了但没赋值。

let empty: null = null;
let notAssigned: undefined = undefined;

strictNullChecks 的威力

在没有 strictNullChecks 的年代(TypeScript 6.x 之前默认关),nullundefined 可以赋给任何类型,这导致大量运行时错误无法在编译期发现。

TypeScript 7.0 默认开启 strict: true,其中包含 strictNullChecks: true 在这个模式下,nullundefined 不能赋给其他类型:

// strictNullChecks: true(7.0 默认)
let name: string = "小明";
// name = null;       // ❌ 不能将 null 赋给 string
// name = undefined;  // ❌ 不能将 undefined 赋给 string

let age: number;
// age = undefined;   // ❌ 也不能

如果你想让一个变量同时接受正常值和 null/undefined,必须用联合类型显式声明:

let name: string | null = "小明";
name = null;  // ✅ 明确声明了允许 null

function findUser(id: number): User | undefined {
  // 找不到时返回 undefined
  return undefined;
}

这个变化看似麻烦,实际省下了无数线上 bug——它强制你在使用之前检查”这个值到底存不存在”。

非空断言 !

在某些场景下,你比 TypeScript 更确定一个值不可能是 nullundefined。比如你初始化了一个 DOM 元素,并且知道它一定存在。这时可以用非空断言 ! 告诉编译器”这个值不是 null”。

let element = document.getElementById("app")!;
// element 的类型是 HTMLElement,不是 HTMLElement | null

let user = getUser();
console.log(user!.name);  // 我确定 user 不是 null/undefined
Warning

非空断言是编译时承诺,不做任何运行时检查。如果你断言错了,运行时还是会崩。用 ! 之前问自己:真的能百分百确定吗?大多数场景用可选链 ?. 和空值合并 ?? 更安全。

never:永远不存在的值

never 是 TypeScript 里最”刚”的类型。它表示永远不可能发生的值。主要有两种场景:

抛出异常的函数

function throwError(message: string): never {
  throw new Error(message);
  // 下面永远不会执行到
}

function fail(): never {
  return throwError("出错了");
}

抛出异常的函数不会正常返回,所以它的返回类型是 never 而非 void

死循环

function infiniteLoop(): never {
  while (true) {
    console.log("永远不会结束...");
  }
}

never 的类型特性

never 是所有类型的子类型——这意味着 never 可以赋给任何类型。反过来,任何类型(除 never 自身以外)都不能赋给 never

let n: never;

// let s: string = "hello";
// n = s;  // ❌ string 不能赋给 never

n = throwError("...");  // ✅ 只有 never 能赋给 never

这个特性让 never 成为穷尽检查的利器。

穷尽检查:never 的实际应用

穷尽检查(exhaustiveness check)确保你处理了所有可能的情况。写 switch 语句时特别有用:

type Shape =
  | { kind: "circle"; radius: number }
  | { kind: "square"; side: 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.side ** 2;
    case "triangle":
      return (shape.base * shape.height) / 2;
    default: {
      // 如果 Shape 后来新增了类型,这里会报错
      let exhaustive: never = shape;
      return exhaustive;
    }
  }
}

这段代码的妙处在于:如果将来有人在 Shape 里加了新的 shape 类型但忘了更新 getAreadefault 分支里的 never 赋值就会报编译错误,提醒你漏了一个 case。

这就是 never 的实战价值——不是用来”写代码”,而是用来”防遗漏”。

四种特殊类型的层级关系

never  →  void  →  null/undefined  →  any/unknown
(最严格)                              (最宽松)

或者换一种理解方式:

  • never:这个值永远不存在
  • void:这个函数不返回有用的东西
  • undefined:这个变量还没赋值
  • null:这个值被主动设为空
  • unknown:我不知道这是什么,先检查
  • any:我不在乎这是什么

可运行示例

// 四种特殊类型综合示例

// 1. void:无返回值的函数
function showWelcome(name: string): void {
  console.log(`欢迎,${name}!`);
}
showWelcome("小明");

// 2. null / undefined 联合类型
function parseAge(input: string): number | null {
  let age = Number(input);
  if (isNaN(age) || age <= 0) {
    return null;  // 解析失败
  }
  return age;
}

let age1 = parseAge("25");
let age2 = parseAge("不是数字");

console.log(`年龄1: ${age1 ?? "无效"}`);  // 25
console.log(`年龄2: ${age2 ?? "无效"}`);  // 无效

// 3. never:穷尽检查
type Animal = "dog" | "cat" | "bird";

function getSound(animal: Animal): string {
  switch (animal) {
    case "dog":  return "汪汪";
    case "cat":  return "喵喵";
    case "bird": return "叽叽";
    default: {
      // 如果新增动物类型忘了处理,这里会报编译错误
      let _check: never = animal;
      return _check;
    }
  }
}

console.log(`狗: ${getSound("dog")}`);
console.log(`猫: ${getSound("cat")}`);

// 4. 非空断言(谨慎使用)
function getConfig(): { port?: number } {
  return { port: 8080 };
}
let config = getConfig();
// if (config.port !== undefined) { ... }  ← 更好的方式
console.log(`端口: ${config.port ?? 3000}`);

输出:

欢迎,小明!
年龄1: 25
年龄2: 无效
狗: 汪汪
猫: 喵喵
端口: 8080

小结

void 给无返回值的函数签名,nullundefined 在 strict 默认开启的 7.0 里会被严格拦截,never 是你防止遗漏分支的安全网。四个类型各司其职,组合起来能让你的代码既灵活又安全。下一章进入”组合类型”——联合类型和交叉类型,学会用 |& 拼接出更复杂的类型。