TypeScript 中的 never 和 void 类型详解

188 阅读2分钟

TypeScript 中的 never 和 void 类型详解

目录

  1. 概述
  2. void 类型
  3. never 类型
  4. void vs never
  5. 实际应用场景
  6. 最佳实践
  7. void 与副作用

概述

TypeScript 中的 voidnever 类型都用于描述函数的返回类型,但它们的语义和使用场景有很大的不同。

void 类型

定义

void 表示函数没有返回值,或者返回 undefined:

  • 用于标识函数不返回任何值
  • 实际上返回 undefined
  • 可以显式返回 undefined

示例

// 基本用法
function logMessage(message: string): void {
  console.log(message);
}

// 显式返回 undefined
function doNothing(): void {
  return undefined;
}

// 箭头函数
const logError = (error: Error): void => {
  console.error(error);
};

// 作为方法
class Logger {
  log(message: string): void {
    console.log(message);
  }
}

特点

  1. void 类型变量只能赋值 undefined 和 null
  2. void 函数可以被调用并继续执行后续代码
  3. 通常用于表示函数的副作用

never 类型

定义

never 表示永远不会返回的函数类型:

  • 函数抛出异常
  • 函数进入无限循环
  • 类型保护中的永远不可能达到的分支

示例

// 抛出异常
function throwError(message: string): never {
  throw new Error(message);
}

// 无限循环
function infiniteLoop(): never {
  while (true) {
    // do something
  }
}

// 类型保护中的 never
function checkType(value: string | number): string {
  if (typeof value === "string") {
    return "string value";
  } else if (typeof value === "number") {
    return "number value";
  } else {
    // value 的类型此时是 never
    const neverValue: never = value;
    return neverValue;
  }
}

// 用于详尽性检查
type Shape = Circle | Square;

function assertNever(value: never): never {
  throw new Error(`Unexpected value: ${value}`);
}

function getArea(shape: Shape) {
  switch (shape.kind) {
    case "circle":
      return Math.PI * shape.radius ** 2;
    case "square":
      return shape.size ** 2;
    default:
      // 如果 Shape 类型添加了新的类型但这里没有处理,TypeScript 会报错
      return assertNever(shape);
  }
}

特点

  1. never 是所有类型的子类型
  2. 没有任何类型是 never 的子类型
  3. 不能给 never 类型变量赋值
  4. 用于表示永远不会发生的情况

void vs never

主要区别

特性voidnever
函数执行会执行完成永不完成
返回值undefined无返回值
后续代码会继续执行不会执行
类型兼容性可以赋值给 any是所有类型的子类型

示例对比

// void 示例
function voidFunction(): void {
  console.log("This will return undefined");
}
const result = voidFunction(); // result 类型是 void
console.log("This will execute");

// never 示例
function neverFunction(): never {
  throw new Error("This will never return");
}
// const result = neverFunction(); // 这行后的代码永远不会执行
console.log("This will not execute");

实际应用场景

void 的应用

  1. 事件处理器
const handleClick = (event: React.MouseEvent): void => {
  event.preventDefault();
  // 执行其他操作
};
  1. 中间件函数
const logMiddleware = (req: Request, res: Response, next: NextFunction): void => {
  console.log(`${req.method} ${req.path}`);
  next();
};

never 的应用

  1. 详尽性检查
type UserStatus = "active" | "inactive" | "banned";

function handleStatus(status: UserStatus) {
  switch (status) {
    case "active":
      return "User is active";
    case "inactive":
      return "User is inactive";
    case "banned":
      return "User is banned";
    default:
      // 如果 UserStatus 添加了新状态但这里没有处理,会报错
      const _exhaustiveCheck: never = status;
      return _exhaustiveCheck;
  }
}
  1. 类型保护
function assertNever(value: never): never {
  throw new Error(`Unexpected value: ${value}`);
}

type ApiResponse = SuccessResponse | ErrorResponse | LoadingResponse;

function handleResponse(response: ApiResponse) {
  if (isSuccess(response)) {
    return response.data;
  } else if (isError(response)) {
    return response.error;
  } else if (isLoading(response)) {
    return "Loading...";
  } else {
    // 确保处理了所有可能的情况
    return assertNever(response);
  }
}

void 与副作用

什么是副作用?

副作用(Side Effects)是指函数除了返回值之外,还对函数外部的状态进行了修改。常见的副作用包括:

  • 修改全局变量
  • 修改传入的参数
  • 进行 API 调用
  • 操作 DOM
  • 写入文件
  • 打印日志

为什么用 void 表示副作用?

  1. 语义明确
// void 清楚地表明这个函数的目的是执行操作而不是返回值
function logMessage(message: string): void {
  console.log(message);  // 副作用:打印日志
}

// 这个函数的重点是修改状态,而不是返回值
function updateUserStatus(userId: string, status: string): void {
  globalUserStore.update(userId, { status });  // 副作用:修改全局状态
}
  1. 区分纯函数和副作用函数
// 纯函数:有返回值,没有副作用
function add(a: number, b: number): number {
  return a + b;
}

// 副作用函数:没有返回值,但有副作用
function saveToLocalStorage(key: string, value: string): void {
  localStorage.setItem(key, value);  // 副作用:修改本地存储
}
  1. React 组件中的事件处理
interface ButtonProps {
  // 事件处理函数通常返回 void,因为它们的目的是执行操作
  onClick: () => void;
  onMouseOver: (event: React.MouseEvent) => void;
}

const Button: React.FC<ButtonProps> = ({ onClick, onMouseOver }) => {
  // 这些处理函数通常包含副作用
  const handleClick = (): void => {
    analytics.trackEvent('button_clicked');  // 副作用:发送分析数据
    onClick();  // 副作用:调用父组件方法
  };

  return <button onClick={handleClick} onMouseOver={onMouseOver}>Click me</button>;
};
  1. 异步操作中的副作用
// Promise<void> 表明这个异步操作的目的是执行副作用
async function syncUserData(userId: string): Promise<void> {
  const userData = await fetchUserData(userId);
  await saveToDatabase(userData);  // 副作用:保存到数据库
  emitEvent('user-synced');       // 副作用:触发事件
}
  1. 中间件和钩子函数
// Express 中间件
const loggingMiddleware = (
  req: Request, 
  res: Response, 
  next: NextFunction
): void => {
  console.log(`${req.method} ${req.path}`);  // 副作用:日志记录
  next();  // 副作用:调用下一个中间件
};

// React useEffect
useEffect((): void => {
  document.title = `User: ${username}`;  // 副作用:修改 DOM
  analytics.trackPageView();             // 副作用:发送分析数据
}, [username]);

void 的优势

  1. 代码意图明确
  • void 返回类型明确表示函数的目的是执行操作而不是计算值
  • 帮助其他开发者理解代码的意图
  • 使代码自文档化
  1. 类型安全
// TypeScript 会确保你不会意外使用函数的返回值
const result = logMessage("Hello");  // result 的类型是 void
result.toString();  // 编译错误:void 类型没有任何方法

// 防止意外返回值
function updateUI(): void {
  document.title = "New Title";
  return "done";  // 错误:void 函数不能返回值
}
  1. 区分函数类别
// 计算函数:返回值
function calculateTotal(items: Item[]): number {
  return items.reduce((sum, item) => sum + item.price, 0);
}

// 操作函数:执行副作用
function displayTotal(total: number): void {
  document.getElementById('total').textContent = `Total: $${total}`;
}

最佳实践

  1. 显式声明副作用
// 好的实践:明确声明返回 void
function initialize(): void {
  setupEventListeners();
  loadInitialData();
  updateUI();
}

// 避免:隐式返回 undefined
function initialize() {
  setupEventListeners();
  loadInitialData();
  updateUI();
}
  1. 分离关注点
// 好的实践:分离计算和副作用
function calculateNewTotal(items: Item[]): number {
  return items.reduce((sum, item) => sum + item.price, 0);
}

function updateTotalDisplay(total: number): void {
  document.getElementById('total').textContent = `$${total}`;
}

// 避免:混合计算和副作用
function processTotal(items: Item[]): number {
  const total = items.reduce((sum, item) => sum + item.price, 0);
  document.getElementById('total').textContent = `$${total}`;
  return total;
}

最佳实践

  1. 使用 void
  • 用于不需要返回值的函数
  • 用于回调函数
  • 用于表示副作用
  1. 使用 never
  • 用于详尽性检查
  • 用于表示不可能的情况
  • 用于类型保护的穷尽性检查

结论

  • voidnever 都是特殊的类型,用于描述函数的返回类型
  • void 表示函数执行完成但不返回值
  • never 表示函数永远不会正常完成
  • 正确使用这两种类型可以提高代码的类型安全性和可维护性