TypeScript联合类型与交叉类型 #

一、联合类型 #

1.1 什么是联合类型 #

联合类型表示一个值可以是多种类型之一,使用|符号连接多个类型。

typescript
type ID = string | number;
type Status = 'pending' | 'approved' | 'rejected';

let id: ID = 'abc123';
id = 123;

let status: Status = 'pending';
status = 'approved';

1.2 基本用法 #

typescript
function printId(id: string | number): void {
    console.log(`ID: ${id}`);
}

printId('abc');
printId(123);
printId(true);

1.3 联合类型的成员 #

typescript
type Primitive = string | number | boolean | null | undefined | symbol | bigint;

type Result = string | number | boolean | null;

二、联合类型收窄 #

2.1 typeof收窄 #

typescript
function printValue(value: string | number): void {
    if (typeof value === 'string') {
        console.log(value.toUpperCase());
    } else {
        console.log(value.toFixed(2));
    }
}

2.2 字面量收窄 #

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

function move(direction: Direction): void {
    switch (direction) {
        case 'up':
            console.log('Moving up');
            break;
        case 'down':
            console.log('Moving down');
            break;
        case 'left':
            console.log('Moving left');
            break;
        case 'right':
            console.log('Moving right');
            break;
    }
}

2.3 in操作符收窄 #

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

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

function move(animal: Bird | Fish): void {
    if ('fly' in animal) {
        animal.fly();
    } else {
        animal.swim();
    }
}

2.4 instanceof收窄 #

typescript
class Dog {
    bark(): void {
        console.log('Woof!');
    }
}

class Cat {
    meow(): void {
        console.log('Meow!');
    }
}

function makeSound(animal: Dog | Cat): void {
    if (animal instanceof Dog) {
        animal.bark();
    } else {
        animal.meow();
    }
}

2.5 自定义类型守卫 #

typescript
interface Fish {
    swim: () => void;
}

interface Bird {
    fly: () => void;
}

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

function move(pet: Fish | Bird): void {
    if (isFish(pet)) {
        pet.swim();
    } else {
        pet.fly();
    }
}

三、联合类型应用 #

3.1 多态参数 #

typescript
function formatValue(value: string | number | boolean): string {
    if (typeof value === 'string') {
        return value;
    } else if (typeof value === 'number') {
        return value.toFixed(2);
    } else {
        return value ? 'true' : 'false';
    }
}

3.2 可选参数 #

typescript
function greet(name: string | undefined): string {
    if (name === undefined) {
        return 'Hello, Guest!';
    }
    return `Hello, ${name}!`;
}

greet('Alice');
greet(undefined);

3.3 函数重载 #

typescript
function createElement(tag: 'a'): HTMLAnchorElement;
function createElement(tag: 'canvas'): HTMLCanvasElement;
function createElement(tag: 'table'): HTMLTableElement;
function createElement(tag: string): HTMLElement {
    return document.createElement(tag);
}

3.4 事件处理 #

typescript
type MouseEvent = {
    type: 'click' | 'dblclick';
    x: number;
    y: number;
};

type KeyboardEvent = {
    type: 'keydown' | 'keyup';
    key: string;
    code: string;
};

type Event = MouseEvent | KeyboardEvent;

function handleEvent(event: Event): void {
    switch (event.type) {
        case 'click':
        case 'dblclick':
            console.log(`Mouse at (${event.x}, ${event.y})`);
            break;
        case 'keydown':
        case 'keyup':
            console.log(`Key: ${event.key}`);
            break;
    }
}

四、交叉类型 #

4.1 什么是交叉类型 #

交叉类型将多个类型合并为一个类型,使用&符号连接多个类型。

typescript
type Name = { name: string };
type Age = { age: number };

type Person = Name & Age;

const person: Person = {
    name: 'Alice',
    age: 25
};

4.2 基本用法 #

typescript
interface BusinessPartner {
    name: string;
    credit: number;
}

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

interface Contact {
    email: string;
    phone: string;
}

type Employee = Identity & Contact;
type Customer = BusinessPartner & Contact;

const employee: Employee = {
    id: 1,
    name: 'Alice',
    email: 'alice@example.com',
    phone: '123-456-7890'
};

4.3 交叉类型的合并 #

typescript
type A = {
    a: string;
};

type B = {
    b: number;
};

type C = A & B & {
    c: boolean;
};

const obj: C = {
    a: 'hello',
    b: 42,
    c: true
};

五、交叉类型应用 #

5.1 混入模式 #

typescript
type Constructor<T = {}> = new (...args: any[]) => T;

function Timestamped<TBase extends Constructor>(Base: TBase) {
    return class extends Base {
        timestamp = Date.now();
    };
}

function Activatable<TBase extends Constructor>(Base: TBase) {
    return class extends Base {
        isActive = false;
        activate() { this.isActive = true; }
        deactivate() { this.isActive = false; }
    };
}

class User {
    constructor(public name: string) {}
}

const TimestampedUser = Timestamped(User);
const ActivatableUser = Activatable(TimestampedUser);

const user = new ActivatableUser('Alice');
user.activate();
console.log(user.timestamp);

5.2 扩展配置 #

typescript
interface BaseConfig {
    apiUrl: string;
    timeout: number;
}

interface AuthConfig {
    apiKey: string;
    secretKey: string;
}

interface LogConfig {
    logLevel: 'debug' | 'info' | 'warn' | 'error';
    logFile?: string;
}

type AppConfig = BaseConfig & AuthConfig & LogConfig;

const config: AppConfig = {
    apiUrl: 'https://api.example.com',
    timeout: 5000,
    apiKey: 'abc123',
    secretKey: 'xyz789',
    logLevel: 'info'
};

5.3 组合函数类型 #

typescript
type Loggable = {
    log(message: string): void;
};

type Serializable = {
    serialize(): string;
};

type Entity = Loggable & Serializable;

class User implements Entity {
    constructor(public name: string, public age: number) {}
    
    log(message: string): void {
        console.log(`[${new Date().toISOString()}] ${message}`);
    }
    
    serialize(): string {
        return JSON.stringify({ name: this.name, age: this.age });
    }
}

六、联合与交叉组合 #

6.1 优先级 #

交叉类型的优先级高于联合类型:

typescript
type A = { a: string };
type B = { b: number };
type C = { c: boolean };

type Union = A | B & C;
type Union2 = A | (B & C);

type Intersection = A & B | C;
type Intersection2 = (A & B) | C;

6.2 组合使用 #

typescript
type Success = {
    status: 'success';
    data: string;
};

type Error = {
    status: 'error';
    error: string;
};

type Loading = {
    status: 'loading';
};

type State = Success | Error | Loading;

type WithTimestamp<T> = T & { timestamp: number };

type TimestampedState = WithTimestamp<State>;

七、类型冲突处理 #

7.1 原始类型冲突 #

typescript
type StringAndNumber = string & number;

let value: StringAndNumber;

7.2 对象属性冲突 #

typescript
type A = {
    prop: string;
};

type B = {
    prop: number;
};

type C = A & B;

let obj: C = {
    prop: 'hello'
};

7.3 函数类型冲突 #

typescript
type Fn1 = (x: string) => void;
type Fn2 = (x: number) => void;

type Combined = Fn1 & Fn2;

const fn: Combined = (x: string | number) => {
    console.log(x);
};

八、可辨识联合 #

8.1 什么是可辨识联合 #

可辨识联合(Discriminated Unions)是一种使用公共属性来区分联合类型成员的模式。

typescript
interface Circle {
    kind: 'circle';
    radius: number;
}

interface Rectangle {
    kind: 'rectangle';
    width: number;
    height: number;
}

interface Triangle {
    kind: 'triangle';
    base: number;
    height: number;
}

type Shape = Circle | Rectangle | Triangle;

8.2 使用可辨识联合 #

typescript
function getArea(shape: Shape): number {
    switch (shape.kind) {
        case 'circle':
            return Math.PI * shape.radius ** 2;
        case 'rectangle':
            return shape.width * shape.height;
        case 'triangle':
            return (shape.base * shape.height) / 2;
    }
}

const circle: Circle = { kind: 'circle', radius: 5 };
const rectangle: Rectangle = { kind: 'rectangle', width: 10, height: 20 };
const triangle: Triangle = { kind: 'triangle', base: 10, height: 15 };

console.log(getArea(circle));
console.log(getArea(rectangle));
console.log(getArea(triangle));

8.3 穷尽性检查 #

typescript
function getArea(shape: Shape): number {
    switch (shape.kind) {
        case 'circle':
            return Math.PI * shape.radius ** 2;
        case 'rectangle':
            return shape.width * shape.height;
        case 'triangle':
            return (shape.base * shape.height) / 2;
        default:
            const _exhaustiveCheck: never = shape;
            return _exhaustiveCheck;
    }
}

8.4 实际应用 #

typescript
interface LoadingState {
    state: 'loading';
}

interface SuccessState<T> {
    state: 'success';
    data: T;
}

interface ErrorState {
    state: 'error';
    error: Error;
}

type AsyncState<T> = LoadingState | SuccessState<T> | ErrorState;

function render<T>(state: AsyncState<T>): string {
    switch (state.state) {
        case 'loading':
            return 'Loading...';
        case 'success':
            return `Data: ${JSON.stringify(state.data)}`;
        case 'error':
            return `Error: ${state.error.message}`;
    }
}

九、实用示例 #

9.1 API响应类型 #

typescript
type SuccessResponse<T> = {
    success: true;
    data: T;
};

type ErrorResponse = {
    success: false;
    error: {
        code: number;
        message: string;
    };
};

type ApiResponse<T> = SuccessResponse<T> | ErrorResponse;

function handleResponse<T>(response: ApiResponse<T>): T {
    if (response.success) {
        return response.data;
    } else {
        throw new Error(response.error.message);
    }
}

9.2 表单字段类型 #

typescript
type TextField = {
    type: 'text';
    name: string;
    label: string;
    placeholder?: string;
    required?: boolean;
};

type NumberField = {
    type: 'number';
    name: string;
    label: string;
    min?: number;
    max?: number;
};

type SelectField = {
    type: 'select';
    name: string;
    label: string;
    options: { value: string; label: string }[];
};

type FormField = TextField | NumberField | SelectField;

function renderField(field: FormField): string {
    switch (field.type) {
        case 'text':
            return `<input type="text" name="${field.name}" placeholder="${field.placeholder || ''}" />`;
        case 'number':
            return `<input type="number" name="${field.name}" min="${field.min || ''}" max="${field.max || ''}" />`;
        case 'select':
            const options = field.options.map(o => `<option value="${o.value}">${o.label}</option>`).join('');
            return `<select name="${field.name}">${options}</select>`;
    }
}

十、总结 #

本章介绍了TypeScript的联合类型和交叉类型:

联合类型要点 #

  1. 使用|连接多个类型
  2. 表示值可以是多种类型之一
  3. 需要类型收窄才能访问特定类型的方法
  4. 常用于多态参数、可选值、可辨识联合

交叉类型要点 #

  1. 使用&连接多个类型
  2. 将多个类型合并为一个类型
  3. 常用于混入模式、扩展配置、组合功能

最佳实践 #

  1. 使用可辨识联合提高类型安全性
  2. 使用类型守卫进行类型收窄
  3. 注意交叉类型的属性冲突
  4. 合理组合联合类型和交叉类型
最后更新:2026-03-26