阿里云主机折上折
  • 微信号
Current Site:Index > The `never` type and the `void` type

The `never` type and the `void` type

Author:Chuan Chen 阅读数:15154人阅读 分类: TypeScript

In TypeScript, never and void are two special types used to represent function return values, but their semantics and purposes are distinctly different. void indicates that a function does not return a value, while never indicates that a function will never return normally. Understanding their differences is crucial for writing type-safe code.

Basic Concept of void

The void type signifies that a function does not return a value. When the function completes execution, it returns nothing (or more precisely, returns undefined). This is the default behavior of functions in JavaScript, and TypeScript explicitly annotates this using the void type. For example:

function logMessage(message: string): void {
  console.log(message);
}

Here, the logMessage function has no return statement, so its return type is void. If you attempt to explicitly return a value, TypeScript will raise an error:

function invalidVoidExample(): void {
  return "This will cause an error"; // Error: Type 'string' is not assignable to type 'void'
}

Relationship Between void and undefined

In TypeScript, void and undefined may seem similar but have subtle differences. void means "no return value," while undefined is a concrete value. If a function explicitly returns undefined, its type can be either void or undefined:

function returnsUndefined(): undefined {
  return undefined; // Valid
}

function alsoReturnsUndefined(): void {
  return undefined; // Also valid, because void allows returning undefined
}

But the reverse is not true:

function invalidUndefinedExample(): undefined {
  // Error: A function whose declared type is neither 'void' nor 'any' must return a value
}

Basic Concept of never

The never type indicates that a function will never return normally. It is typically used in the following scenarios:

  1. Functions that always throw an exception.
  2. Functions containing infinite loops.
  3. Branches that should never exist after type narrowing.

Functions That Throw Exceptions

If a function always throws an error, it never has a chance to return a value, so its return type is never:

function throwError(message: string): never {
  throw new Error(message);
}

Functions with Infinite Loops

Similarly, if a function contains an infinite loop, its return type is also never:

function infiniteLoop(): never {
  while (true) {
    // Infinite execution
  }
}

never in Type Narrowing

In type guards or conditional types, never can represent branches that should never exist. For example:

type NonString<T> = T extends string ? never : T;

function filterStrings<T>(value: T): NonString<T> {
  if (typeof value === "string") {
    throw new Error("Strings are not allowed");
  }
  return value; // Type is NonString<T>
}

Key Differences Between void and never

  1. Semantic Difference

    • void: The function completes execution normally but returns no value.
    • never: The function cannot complete execution normally.
  2. Assignability

    • void can be assigned to undefined or null (when strictNullChecks is off).
    • never is a subtype of all types and can be assigned to any type, but no type can be assigned to never (except never itself).
let v: void = undefined; // Valid
let n: never = throwError("error"); // Only assignable via a never-returning function

// The following will raise an error
let invalidNever: never = 42; // Error: Type 'number' is not assignable to type 'never'
  1. Behavior in Union Types
    • void is preserved in union types.
    • never is ignored in union types because it represents "impossible to exist."
type UnionWithVoid = string | void; // string | void
type UnionWithNever = string | never; // string

Practical Use Cases

Typical Uses of void

  1. Event Handlers
    Many event handlers do not return a value, making void suitable:
button.addEventListener("click", (): void => {
  console.log("Button clicked");
});
  1. Side Effects in React Components
    In React, if a component method is only used for side effects, it can be declared as void:
class MyComponent extends React.Component {
  handleClick(): void {
    this.setState({ clicked: true });
  }
}

Typical Uses of never

  1. Exhaustiveness Checking
    In switch or if-else chains, never ensures all possible cases are handled:
type Shape = "circle" | "square" | "triangle";

function getArea(shape: Shape): number {
  switch (shape) {
    case "circle":
      return Math.PI * 2;
    case "square":
      return 4;
    case "triangle":
      return 3;
    default:
      const exhaustiveCheck: never = shape; // If Shape adds new types, this will error
      throw new Error(`Unknown shape: ${exhaustiveCheck}`);
  }
}
  1. Higher-Order Type Utilities
    In conditional types, never is often used to filter or combine types:
type ExtractStrings<T> = T extends string ? T : never;

type Result = ExtractStrings<"a" | 1 | true>; // "a"

Common Pitfalls and Misconceptions

  1. Confusing void and undefined
    Although void allows returning undefined, they are different concepts. void is a type annotation, while undefined is a value.

  2. Misusing never as a Variable Type
    Declaring a variable of type never is usually meaningless because it cannot be assigned (except via a never-returning function):

let x: never; // Cannot be assigned
x = 42; // Error
  1. Ignoring never in Type Narrowing
    In complex type logic, failing to use never properly can lead to type safety issues. For example:
function badTypeGuard(value: string | number) {
  if (typeof value === "string") {
    return value.toUpperCase();
  }
  if (typeof value === "number") {
    return value.toFixed(2);
  }
  // Here, value is of type never, but if unhandled, it may hide bugs
}

Interactions in Advanced Type Systems

Behavior of void in Generics

When void is used as a generic parameter, it is treated as a regular type:

type Wrapper<T> = { value: T };
const wrappedVoid: Wrapper<void> = { value: undefined }; // Valid

never and Conditional Types

In conditional types, never triggers distributive behavior:

type Distributed<T> = T extends any ? T[] : never;
type Result = Distributed<"a" | never>; // "a"[]

Function Type Compatibility

void return types have special rules in function compatibility. When a function type is declared as void, the actual implementation can return any value (though the value will be ignored):

type VoidFunc = () => void;
const fn: VoidFunc = () => "hello"; // No error, but the return value is ignored

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

Front End Chuan

Front End Chuan, Chen Chuan's Code Teahouse 🍵, specializing in exorcising all kinds of stubborn bugs 💻. Daily serving baldness-warning-level development insights 🛠️, with a bonus of one-liners that'll make you laugh for ten years 🐟. Occasionally drops pixel-perfect romance brewed in a coffee cup ☕.