阿里云主机折上折
  • 微信号
Current Site:Index > Union types and intersection types

Union types and intersection types

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

Basic Concepts of Union Types and Intersection Types

In TypeScript, union types and intersection types are two important ways of combining types in the type system. Union types, denoted by |, allow a value to be one of several types; intersection types, denoted by &, merge multiple types into a single type. These two type operators provide flexible type composition capabilities, enabling more precise descriptions of complex data structures.

// Union type example
type StringOrNumber = string | number;

// Intersection type example
type Named = { name: string };
type Aged = { age: number };
type Person = Named & Aged;

In-Depth Analysis of Union Types

Union types are most commonly used in real-world development to handle variables that may be of multiple types. When TypeScript encounters a union type, it requires that only members common to all types can be accessed unless type guards are used to narrow the type.

function printId(id: string | number) {
  if (typeof id === 'string') {
    console.log(id.toUpperCase());
  } else {
    console.log(id.toFixed(2));
  }
}

Union types can also be combined with literal types to create more precise type definitions:

type Direction = 'up' | 'down' | 'left' | 'right';

function move(direction: Direction) {
  // ...
}

Practical Applications of Intersection Types

The primary purpose of intersection types is to merge multiple types into a single type, where the new type includes all properties of the original types. This is particularly useful when combining multiple interfaces or type aliases.

interface Employee {
  id: number;
  name: string;
}

interface Manager {
  department: string;
  subordinates: Employee[];
}

type ManagerEmployee = Employee & Manager;

const me: ManagerEmployee = {
  id: 1,
  name: 'John',
  department: 'Engineering',
  subordinates: []
};

Intersection types excel when working with mixin patterns:

class Disposable {
  dispose() {
    console.log('Disposing...');
  }
}

class Activatable {
  activate() {
    console.log('Activating...');
  }
}

type SmartObject = Disposable & Activatable;

function createSmartObject(): SmartObject {
  const result = {} as SmartObject;
  Object.assign(result, new Disposable(), new Activatable());
  return result;
}

Differences Between Union Types and Intersection Types

Although both are type composition operators, union types represent an "or" relationship, while intersection types represent an "and" relationship. Understanding their differences is crucial for correct usage.

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

// Union type: can be either A or B
type AOrB = A | B;
const aOrB1: AOrB = { a: 1 }; // valid
const aOrB2: AOrB = { b: 'hello' }; // valid

// Intersection type: must be both A and B
type AAndB = A & B;
const aAndB: AAndB = { a: 1, b: 'hello' }; // valid

Union and Intersection in Advanced Type Operations

Union types and intersection types can be combined with other TypeScript features to create a more powerful type system.

Distributive Property in Conditional Types

type Box<T> = { value: T };
type Unbox<T> = T extends Box<infer U> ? U : T;

type StringOrNumberBox = Box<string> | Box<number>;
type Unboxed = Unbox<StringOrNumberBox>; // string | number

Mapped Types with Union/Intersection

type PartialBy<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;

interface User {
  id: number;
  name: string;
  email: string;
}

type PartialUser = PartialBy<User, 'id' | 'email'>;
/*
Equivalent to:
{
  name: string;
  id?: number;
  email?: string;
}
*/

Union and Intersection in Utility Types

TypeScript's built-in utility types heavily use union and intersection types:

// Implementation principle of Partial<T>
type Partial<T> = {
  [P in keyof T]?: T[P];
};

// Implementation principle of Required<T>
type Required<T> = {
  [P in keyof T]-?: T[P];
};

// Implementation principle of Readonly<T>
type Readonly<T> = {
  readonly [P in keyof T]: T[P];
};

Type Inference with Union and Intersection

TypeScript's type inference has specific behaviors when handling union and intersection types:

function firstElement<T>(arr: T[]): T | undefined {
  return arr[0];
}

const nums = [1, 2, 3];
const num = firstElement(nums); // number | undefined

const mixed = [1, 'two', true];
const first = firstElement(mixed); // number | string | boolean | undefined

Function Overloads and Union Types

Union types can simplify function overload syntax:

// Traditional overload syntax
function padLeft(value: string, padding: number): string;
function padLeft(value: string, padding: string): string;
function padLeft(value: string, padding: any): string {
  // implementation
}

// Simplified with union type
function padLeft(value: string, padding: number | string): string {
  if (typeof padding === 'number') {
    return Array(padding + 1).join(' ') + value;
  }
  return padding + value;
}

Type Guards and Union Types

Type guards are essential tools for working with union types:

interface Bird {
  fly(): void;
  layEggs(): void;
}

interface Fish {
  swim(): void;
  layEggs(): void;
}

function getSmallPet(): Fish | Bird {
  // ...
}

const pet = getSmallPet();

// Using 'in' operator type guard
if ('fly' in pet) {
  pet.fly();
} else {
  pet.swim();
}

// Using custom type predicate
function isFish(pet: Fish | Bird): pet is Fish {
  return (pet as Fish).swim !== undefined;
}

Type Merging in Intersection Types

How intersection types handle properties with the same name:

type A = { x: number; y: string };
type B = { x: string; z: boolean };

type C = A & B;
/*
Type C is:
{
  x: never;  // number & string → no value can be both number and string
  y: string;
  z: boolean;
}
*/

Union Types in React Applications

React's prop type definitions frequently use union types:

type ButtonProps = {
  size: 'small' | 'medium' | 'large';
  variant: 'primary' | 'secondary' | 'tertiary';
  disabled?: boolean;
  onClick?: () => void;
};

const Button: React.FC<ButtonProps> = ({ size, variant, disabled, onClick }) => {
  // component implementation
};

Extending Component Props with Intersection Types

In React higher-order components, intersection types are used to merge props:

interface WithLoadingProps {
  loading: boolean;
}

function withLoading<P extends object>(Component: React.ComponentType<P>) {
  return function WithLoading(props: P & WithLoadingProps) {
    return props.loading ? <div>Loading...</div> : <Component {...props} />;
  };
}

// Usage
const EnhancedButton = withLoading(Button);

Template Literal Types and Unions

Template literal types introduced in TypeScript 4.1 can be combined with union types:

type VerticalAlignment = 'top' | 'middle' | 'bottom';
type HorizontalAlignment = 'left' | 'center' | 'right';

type Alignment = `${VerticalAlignment}-${HorizontalAlignment}`;
// Result: "top-left" | "top-center" | "top-right" | 
//         "middle-left" | "middle-center" | "middle-right" | 
//         "bottom-left" | "bottom-center" | "bottom-right"

Index Access Types and Unions

Accessing index types through union types:

interface Person {
  name: string;
  age: number;
  address: {
    street: string;
    city: string;
  };
}

type PersonProperty = keyof Person; // "name" | "age" | "address"

function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

const person: Person = { name: 'John', age: 30, address: { street: 'Main', city: 'NY' } };
const age = getProperty(person, 'age'); // number

Conditional Types and Union Distribution

Conditional types automatically distribute over union types:

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

type StrOrNumArray = ToArray<string | number>; // string[] | number[]

// Equivalent to
type StrOrNumArray = ToArray<string> | ToArray<number>;

Intersection Types and Generic Constraints

Intersection types can enhance generic constraints:

function merge<T extends object, U extends object>(first: T, second: U): T & U {
  return { ...first, ...second };
}

const merged = merge({ name: 'John' }, { age: 30 });
// { name: 'John', age: 30 }

Union Types in Redux Applications

Redux action types are typically defined using union types:

type Action = 
  | { type: 'ADD_TODO'; text: string }
  | { type: 'TOGGLE_TODO'; index: number }
  | { type: 'SET_VISIBILITY_FILTER'; filter: string };

function todos(state = [], action: Action) {
  switch (action.type) {
    case 'ADD_TODO':
      return [...state, { text: action.text, completed: false }];
    case 'TOGGLE_TODO':
      return state.map((todo, index) =>
        index === action.index ? { ...todo, completed: !todo.completed } : todo
      );
    default:
      return state;
  }
}

Intersection Types and Function Overloads

Intersection types can merge multiple function types:

type Func1 = (a: number) => number;
type Func2 = (b: string) => string;

type CombinedFunc = Func1 & Func2;

// Implementation must handle all cases
const func: CombinedFunc = (arg: number | string) => {
  if (typeof arg === 'number') {
    return arg * 2;
  } else {
    return arg.toUpperCase();
  }
};

Union Types and the never Type

The never type has special behavior in union types:

type T = string | never; // equivalent to string
type U = string & never; // equivalent to never

Intersection Types and Interface Inheritance

Intersection types can achieve effects similar to interface inheritance:

interface A {
  a: number;
}

interface B extends A {
  b: string;
}

// Similar effect using intersection type
type C = A & { b: string };

Union Types in API Response Handling

Union types can represent different response states when handling API responses:

type ApiResponse<T> = 
  | { status: 'loading' }
  | { status: 'success'; data: T }
  | { status: 'error'; error: Error };

function handleResponse<T>(response: ApiResponse<T>) {
  switch (response.status) {
    case 'loading':
      return 'Loading...';
    case 'success':
      return response.data;
    case 'error':
      return `Error: ${response.error.message}`;
  }
}

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

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