阿里云主机折上折
  • 微信号
您当前的位置:网站首页 > 联合类型与交叉类型

联合类型与交叉类型

作者:陈川 阅读数:48695人阅读 分类: TypeScript

联合类型与交叉类型的基本概念

TypeScript中的联合类型与交叉类型是类型系统中两种重要的组合方式。联合类型用|表示,允许一个值属于多种类型之一;交叉类型用&表示,将多个类型合并为一个类型。这两种类型操作符提供了灵活的类型组合能力,能够更精确地描述复杂的数据结构。

// 联合类型示例
type StringOrNumber = string | number;

// 交叉类型示例
type Named = { name: string };
type Aged = { age: number };
type Person = Named & Aged;

联合类型的深入解析

联合类型在实际开发中最常见的应用场景是处理可能为多种类型的变量。当TypeScript遇到联合类型时,它会要求只能访问所有类型共有的成员,除非使用类型守卫缩小类型范围。

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

联合类型还可以与字面量类型结合,创建更精确的类型定义:

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

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

交叉类型的实际应用

交叉类型的主要作用是将多个类型合并为一个类型,新类型将包含所有原类型的属性。这在组合多个接口或类型别名时特别有用。

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: []
};

交叉类型在处理混入(mixin)模式时表现出色:

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;
}

联合类型与交叉类型的区别

虽然两者都是类型组合操作符,但联合类型表示"或"的关系,交叉类型表示"与"的关系。理解它们的区别对于正确使用至关重要。

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

// 联合类型:可以是A或B
type AOrB = A | B;
const aOrB1: AOrB = { a: 1 }; // 合法
const aOrB2: AOrB = { b: 'hello' }; // 合法

// 交叉类型:必须同时是A和B
type AAndB = A & B;
const aAndB: AAndB = { a: 1, b: 'hello' }; // 合法

高级类型操作中的联合与交叉

联合类型和交叉类型可以与其他TypeScript特性结合,创建更强大的类型系统。

条件类型中的分布特性

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

映射类型与联合/交叉

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'>;
/*
等效于:
{
  name: string;
  id?: number;
  email?: string;
}
*/

实用类型中的联合与交叉

TypeScript内置的实用类型大量使用了联合和交叉类型:

// Partial<T> 的实现原理
type Partial<T> = {
  [P in keyof T]?: T[P];
};

// Required<T> 的实现原理
type Required<T> = {
  [P in keyof T]-?: T[P];
};

// Readonly<T> 的实现原理
type Readonly<T> = {
  readonly [P in keyof T]: T[P];
};

类型推断与联合交叉

TypeScript的类型推断在处理联合和交叉类型时有特定行为:

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 padLeft(value: string, padding: number): string;
function padLeft(value: string, padding: string): string;
function padLeft(value: string, padding: any): string {
  // 实现
}

// 使用联合类型简化
function padLeft(value: string, padding: number | string): string {
  if (typeof padding === 'number') {
    return Array(padding + 1).join(' ') + value;
  }
  return padding + value;
}

类型守卫与联合类型

类型守卫是处理联合类型的重要工具:

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

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

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

const pet = getSmallPet();

// 使用in操作符类型守卫
if ('fly' in pet) {
  pet.fly();
} else {
  pet.swim();
}

// 使用自定义类型谓词
function isFish(pet: Fish | Bird): pet is Fish {
  return (pet as Fish).swim !== undefined;
}

交叉类型的类型合并

交叉类型在合并时如何处理同名属性:

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

type C = A & B;
/*
C的类型为:
{
  x: never;  // number & string → 没有值可以同时是number和string
  y: string;
  z: boolean;
}
*/

联合类型在React中的应用

React的props类型定义经常使用联合类型:

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

const Button: React.FC<ButtonProps> = ({ size, variant, disabled, onClick }) => {
  // 组件实现
};

交叉类型扩展组件props

在React高阶组件中,交叉类型用于合并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} />;
  };
}

// 使用
const EnhancedButton = withLoading(Button);

模板字面量类型与联合

TypeScript 4.1引入的模板字面量类型可以与联合类型结合:

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

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

索引访问类型与联合

通过联合类型访问索引类型:

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

条件类型与联合分布

条件类型在处理联合类型时会自动分布:

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

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

// 等同于
type StrOrNumArray = ToArray<string> | ToArray<number>;

交叉类型与泛型约束

交叉类型可以用于增强泛型约束:

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 }

联合类型在Redux中的应用

Redux的action类型通常使用联合类型定义:

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;
  }
}

交叉类型与函数重载

交叉类型可以用于合并多个函数类型:

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

type CombinedFunc = Func1 & Func2;

// 实际实现需要处理所有情况
const func: CombinedFunc = (arg: number | string) => {
  if (typeof arg === 'number') {
    return arg * 2;
  } else {
    return arg.toUpperCase();
  }
};

联合类型与never类型

never类型在联合类型中有特殊行为:

type T = string | never; // 等价于 string
type U = string & never; // 等价于 never

交叉类型与接口继承

交叉类型可以实现类似接口继承的效果:

interface A {
  a: number;
}

interface B extends A {
  b: string;
}

// 使用交叉类型实现类似效果
type C = A & { b: string };

联合类型在API响应处理中的应用

处理API响应时,联合类型可以表示不同的响应状态:

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

前端川

前端川,陈川的代码茶馆🍵,专治各种不服的Bug退散符💻,日常贩卖秃头警告级的开发心得🛠️,附赠一行代码笑十年的摸鱼宝典🐟,偶尔掉落咖啡杯里泡开的像素级浪漫☕。‌