阿里云主机折上折
  • 微信号
Current Site:Index > Type hierarchy and bottom type

Type hierarchy and bottom type

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

The type hierarchy and bottom type are two key concepts in TypeScript's type system. Understanding them helps in building more robust type constraints, especially when dealing with complex type relationships and edge cases. The type hierarchy determines the compatibility rules between types, while the bottom type represents the lowest-level type in the type system.

Basic Concepts of Type Hierarchy

The type hierarchy in TypeScript is similar to the inclusion relationship in set theory. When type A can be assigned to type B, we say that A is a subtype of B, or B is a supertype of A. This relationship forms the skeleton of the type system.

let a: string = "hello";
let b: String = a; // Valid, because string is a subtype of String

The hierarchy of basic types is as follows:

  • never <: all types
  • Literal types <: corresponding primitive types
  • Primitive types <: corresponding wrapper types
  • All types <: unknown

Top Type and Bottom Type

In the type hierarchy, unknown is the top type, which can accept values of any type. On the other hand, never is the bottom type, which cannot accept any value.

function acceptAny(value: unknown) {
  // Can accept any parameter
}

function rejectAll(value: never) {
  // Cannot accept any actual parameter
}

The uniqueness of the bottom type never lies in:

  1. It is a subtype of all types.
  2. No value can be assigned to the never type.
  3. A function returning never indicates it will never return normally.

Type Compatibility Rules

TypeScript uses a structural type system, where type compatibility is based on the structure of types rather than their declarations. This leads to some interesting hierarchical relationships:

interface Point {
  x: number;
  y: number;
}

interface Point3D extends Point {
  z: number;
}

let p1: Point = { x: 0, y: 10 };
let p2: Point3D = { x: 0, y: 10, z: 20 };
p1 = p2; // Valid, Point3D is a subtype of Point

Hierarchy of Union Types and Intersection Types

Union types and intersection types exhibit different behaviors in the type hierarchy:

type A = { a: number };
type B = { b: string };

// Intersection types produce subtypes
type AB = A & B; // AB <: A, AB <: B

// Union types produce supertypes
type AOrB = A | B; // A <: AOrB, B <: AOrB

Practical Use Cases of never

The bottom type never has several important applications in real-world development:

  1. Representing impossible branches:
function assertNever(x: never): never {
  throw new Error("Unexpected object: " + x);
}

function handleShape(shape: Circle | Square) {
  switch (shape.kind) {
    case "circle":
      return shape.radius * Math.PI;
    case "square":
      return shape.sideLength ** 2;
    default:
      return assertNever(shape); // Ensures all cases are handled
  }
}
  1. Filtering union types:
type OnlyStrings<T> = T extends string ? T : never;
type Result = OnlyStrings<"a" | "b" | 1 | 2>; // "a" | "b"
  1. Representing functions that never return:
function infiniteLoop(): never {
  while (true) {}
}

never in Type Inference

In conditional type inference, never exhibits special behavior:

type TryInfer<T> = T extends { a: infer A } ? A : never;

type Test1 = TryInfer<{ a: string }>; // string
type Test2 = TryInfer<{}>; // never

Type Hierarchy and Conditional Types

Conditional types leverage the type hierarchy to perform complex type operations:

type IsSubtype<T, U> = T extends U ? true : false;

type Test1 = IsSubtype<string, String>; // true
type Test2 = IsSubtype<"literal", string>; // true
type Test3 = IsSubtype<never, unknown>; // true

never in Distributive Conditional Types

When never appears in distributive conditional types, it is automatically filtered out:

type ToArray<T> = T extends any ? T[] : never;

type Test = ToArray<string | number>; // string[] | number[]
type TestNever = ToArray<never>; // never

Type Hierarchy and Mapped Types

Mapped types also exhibit special behavior when handling never:

type FilterProperties<T, U> = {
  [K in keyof T as T[K] extends U ? K : never]: T[K]
};

interface Example {
  name: string;
  age: number;
  flag: boolean;
}

type StringProps = FilterProperties<Example, string>; // { name: string }

never in Type Predicates

In user-defined type guards, never can be used to represent impossible cases:

function isStringArray(value: unknown): value is string[] {
  return Array.isArray(value) && value.every(item => typeof item === "string");
}

function processValue(value: string[] | number[]) {
  if (isStringArray(value)) {
    // value is string[]
  } else {
    // value is number[]
    // If the isStringArray type predicate is incorrect, a never type may remain here
  }
}

never in Type Parameter Constraints

never can be used as a type parameter constraint to indicate "no type is allowed":

function forbidden<T extends never>() {}

forbidden(); // Error: Type parameter required
forbidden<string>(); // Error: string does not satisfy never constraint

never in Recursive Types

In recursive types, never is often used as a termination condition:

type DeepReadonly<T> = T extends object
  ? { readonly [K in keyof T]: DeepReadonly<T[K]> }
  : T;

type Test = DeepReadonly<{ a: { b: number } }>;
// { readonly a: { readonly b: number } }

Type Hierarchy and Function Types

Function parameter types are contravariant, while return types are covariant:

type Func = (arg: string) => void;

let func1: Func = (arg: string) => {};
let func2: Func = (arg: "literal") => {}; // Error
let func3: Func = (arg: unknown) => {}; // Valid

Type Hierarchy and Class Inheritance

Class inheritance relationships also follow type hierarchy rules:

class Animal {
  move() {}
}

class Dog extends Animal {
  bark() {}
}

let animal: Animal = new Dog(); // Valid
// animal.bark(); // Error: bark method does not exist on Animal type

Type Assertions and Hierarchy

Type assertions can bypass type checking but must satisfy certain hierarchical relationships:

let value = "hello" as unknown as number; // Not recommended, but technically possible

Type Hierarchy and Template Literal Types

Template literal types also participate in the type hierarchy:

type Color = "red" | "green" | "blue";
type UpperColor = Uppercase<Color>; // "RED" | "GREEN" | "BLUE"

let color: Color = "red";
let upperColor: UpperColor = "RED";
// color = upperColor; // Error
// upperColor = color; // Error

Type Hierarchy and Indexed Access

When accessing types via indexing, never appears for non-existent properties:

type Example = { a: string; b: number };
type A = Example["a"]; // string
type C = Example["c"]; // Error: Property 'c' does not exist
type All = Example[keyof Example]; // string | number

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

如果侵犯了你的权益请来信告知我们删除。邮箱: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 ☕.