TypeScript 高级类型详解

一、简介

TypeScript 除了基础类型外,还提供了一系列高级类型特性,这些特性通过组合、转换和约束基础类型,解决了复杂场景下的类型定义问题。本文将详细讲解 TypeScript 高级类型的核心概念、使用场景及实战技巧,帮助开发者写出更精准、更灵活的类型标注。

二、交叉类型(Intersection Types)

交叉类型(A & B)用于将多个类型合并为一个新类型,新类型包含所有源类型的成员。可以理解为“同时满足所有类型的约束”,类似于逻辑上的“与(AND)”操作。

2.1 基础用法

// 定义两个基础类型
type User = { id: number; name: string };
type Contact = { phone: string; email: string };

// 交叉类型:同时包含 User 和 Contact 的所有属性
type UserWithContact = User & Contact;

// 必须满足所有类型的约束
const user: UserWithContact = {
  id: 1,
  name: "懒羊羊",
  phone: "110",
  email: "lanyangyang@qq.com"
};

2.2 特殊场景:交叉类型与原始类型

当交叉类型涉及原始类型时,只有“兼容的类型”才能交叉出有效结果:

// 交叉两个相同的原始类型,结果仍为该类型
type NumAndNum = number & number; // 等价于 number

// 交叉不兼容的原始类型,结果为 never(无有效值)
type StrAndNum = string & number; // 等价于 never(没有值能同时是 string 和 number)

// 交叉函数类型(合并参数和返回值约束)
type F1 = (a: number) => string;
type F2 = (b: string) => number;
type F3 = F1 & F2;
// F3 等价于 (a: number & string) => string & number → (a: never) => never
const f3: F3 = () => {
  throw new Error();
};

2.3 实用场景:扩展对象类型

交叉类型常用于“给现有类型动态添加属性”,避免重复定义:

// 基础用户类型
type BaseUser = { id: number; name: string };

// 扩展类型:添加时间戳属性
type TimedUser = BaseUser & { createdAt: Date; updatedAt: Date };

// 扩展类型:添加权限属性
type AdminUser = BaseUser & { role: "admin"; permissions: string[] };

三、联合类型(Union Types)

联合类型(A | B)表示一个值可以是多个类型中的任意一种,类似于逻辑上的“或(OR)”操作。与交叉类型的“同时满足”不同,联合类型是“满足其一即可”。

3.1 基础用法

// 联合类型:值可以是 string 或 number
type StringOrNumber = string | number;

let value: StringOrNumber;
value = "hello"; // 正确
value = 123; // 正确
// value = true; // 错误:boolean 不在联合类型中

3.2 类型收窄(Type Narrowing)

使用联合类型时,需通过类型判断(如 typeofinstanceofin 等)“收窄”类型范围,才能安全访问特定类型的属性:

type User = { type: "user"; name: string };
type Admin = { type: "admin"; role: string };
type Person = User | Admin;

function logPerson(person: Person) {
  // 错误:联合类型中并非所有类型都有 name 属性
  // console.log(person.name); 

  // 方式1:通过字面量类型收窄(最常用)
  if (person.type === "user") {
    // 此时 TypeScript 自动推断为 User 类型
    console.log("用户:", person.name); 
  } else {
    // 此时自动推断为 Admin 类型
    console.log("管理员:", person.role); 
  }

  // 方式2:通过 in 运算符检查属性
  if ("name" in person) {
    console.log(person.name);
  } else {
    console.log(person.role);
  }
}

3.3 可区分联合(Discriminated Unions)

可区分联合是一种特殊的联合类型,通过一个“公共属性”(通常称为 tag)来区分不同类型,是处理多状态场景的最佳实践:

// 定义三种状态类型,均包含 type 作为区分标识
type LoadingState = { type: "loading" };
type SuccessState = { type: "success"; data: string };
type ErrorState = { type: "error"; message: string };

// 可区分联合:三种状态的集合
type RequestState = LoadingState | SuccessState | ErrorState;

// 根据 type 自动收窄类型
function handleState(state: RequestState) {
  switch (state.type) {
    case "loading":
      console.log("加载中...");
      break;
    case "success":
      // 自动推断为 SuccessState,可安全访问 data
      console.log("成功:", state.data.toUpperCase()); 
      break;
    case "error":
      // 自动推断为 ErrorState,可安全访问 message
      console.log("失败:", state.message); 
      break;
  }
}

四、类型别名(Type Aliases)

类型别名(type)用于给现有类型起一个新名字,类似于变量的命名,但作用于类型。它可以简化复杂类型的引用,也能定义无法用接口(interface)表达的类型(如联合类型、交叉类型)。

4.1 基础用法

// 给基础类型起别名
type Age = number;
let userAge: Age = 25; // 等价于 number

// 给联合类型起别名
type ID = string | number;
let userId: ID = "123"; // 正确
userId = 456; // 正确

// 给对象类型起别名
type Point = { x: number; y: number };
const p: Point = { x: 10, y: 20 };

4.2 与接口(Interface)的区别

类型别名和接口都可用于定义对象类型,但存在关键差异:

// 类型别名:不可重复定义,重复会报错
type User = { name: string };
// type User = { age: number }; // 错误:标识符“User”重复

// 接口:可重复定义,自动合并
interface User { name: string }
interface User { age: number } // 正确:合并为 { name: string; age: number }

// 类型别名支持联合/交叉类型,接口不支持
type A = { a: number } | { b: string }; // 正确
// interface A = { a: number } | { b: string }; // 错误

选择原则

  • 若需“合并类型”或定义“类的继承结构”,用 interface
  • 若需定义联合类型、交叉类型或给基础类型起别名,用 type

五、索引类型(Index Types)

索引类型用于通过“索引”动态访问对象的属性类型,结合 keyof 操作符和索引访问类型(T[K]),可实现对对象属性的类型安全操作。

5.1 keyof 操作符

keyof T 用于获取类型 T 的所有属性名组成的联合类型,相当于“属性名的元数据”:

type User = { id: number; name: string; age: number };

// keyof User 等价于 "id" | "name" | "age"
type UserKeys = keyof User; 

let key: UserKeys;
key = "id"; // 正确
key = "name"; // 正确
// key = "email"; // 错误:"email" 不是 User 的属性

5.2 索引访问类型(T[K])

T[K] 用于获取类型 T 中属性 K 的类型,K 可以是单个属性名或联合类型:

type User = { id: number; name: string; age: number };

// 获取单个属性的类型:User["id"] 等价于 number
type IDType = User["id"]; 

// 获取多个属性的类型:联合类型
type NameOrAge = User["name" | "age"]; // 等价于 string | number

// 结合 keyof 获取所有属性的类型联合
type UserValueTypes = User[keyof User]; // 等价于 number | string

5.3 实战场景:类型安全的属性访问函数

利用索引类型可实现“获取对象任意属性值”的函数,同时保证类型安全:

// 函数:获取对象 obj 中属性 key 的值
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

const user = { id: 1, name: "张三", age: 20 };

// 正确:key 必须是 user 的属性,返回值类型自动推断
const id = getProperty(user, "id"); // id: number
const userName = getProperty(user, "name"); // name: string

// 错误:key 不是 user 的属性,编译时直接报错
// const email = getProperty(user, "email");
console.log(id, userName); // 1 "张三"

六、映射类型(Mapped Types)

映射类型用于“基于现有类型创建新类型”,通过遍历源类型的属性(keyof T)并修改其特性(如只读、可选)生成新类型,是 TypeScript 类型编程的核心工具。

6.1 基础语法

// 映射类型语法:遍历 T 的所有属性 K,对每个属性做转换
type MappedType<T> = {
  [K in keyof T]: T[K]; // 此处为“复制源类型”,无修改
};

type User = { id: number; name: string };
type UserCopy = MappedType<User>; // 等价于 User

6.2 常用映射工具类型

TypeScript 内置了多个常用映射类型(位于 lib.es5.d.ts),掌握它们可大幅提升开发效率:

1. Readonly:将所有属性转为只读
type ReadonlyUser = Readonly<User>;
// 等价于:
// {
//   readonly id: number;
//   readonly name: string;
// }

const user: ReadonlyUser = { id: 1, name: "张三" };
// user.id = 2; // 错误:只读属性不可修改
2. Partial:将所有属性转为可选
type PartialUser = Partial<User>;
// 等价于:
// {
//   id?: number;
//   name?: string;
// }

// 适合“部分更新”场景,无需传递所有属性
function updateUser(user: User, changes: PartialUser): User {
  return { ...user, ...changes };
}
3. Required:将所有属性转为必选(与 Partial 相反)
type OptionalUser = { id?: number; name?: string };
type RequiredUser = Required<OptionalUser>; // 等价于 { id: number; name: string }
4. Pick<T, K>:从 T 中选取部分属性 K 组成新类型
// 从 User 中选取 "name" 属性
type UserName = Pick<User, "name">; // 等价于 { name: string }

// 选取多个属性(联合类型)
type UserIdAndName = Pick<User, "id" | "name">; // 等价于 { id: number; name: string }
5. Omit<T, K>:从 T 中排除部分属性 K 组成新类型(与 Pick 相反)
// 从 User 中排除 "age" 属性
type UserWithoutAge = Omit<User, "age">; // 等价于 { id: number; name: string }

6.3 自定义映射类型

可根据需求自定义映射规则,例如“将所有属性类型转为字符串”:

// 自定义映射:将 T 的所有属性值类型转为 string
type Stringify<T> = {
  [K in keyof T]: string;
};

type User = { id: number; age: number };
type StringifiedUser = Stringify<User>; 
// 等价于 { id: string; age: string }

七、条件类型(Conditional Types)

条件类型用于“根据条件判断选择类型”,语法为 T extends U ? X : Y(若 T 是 U 的子类型,则返回 X,否则返回 Y),支持嵌套和递归,是实现复杂类型逻辑的基础。

7.1 基础用法

// 条件类型:若 T 是 string,则返回 number,否则返回 boolean
type Conditional<T> = T extends string ? number : boolean;

type A = Conditional<string>; // A = number(T 是 string)
type B = Conditional<number>; // B = boolean(T 不是 string)

7.2 分布式条件类型

当条件类型的泛型参数是联合类型时,会自动“分布式”应用到每个成员:

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

// 分布式应用:string → string[],number → number[]
type StrOrNumArray = ToArray<string | number>; // 等价于 string[] | number[]

7.3 常用内置条件类型

TypeScript 内置了多个实用条件类型:

1. Exclude<T, U>:从 T 中排除可赋值给 U 的类型
type T = "a" | "b" | "c";
type U = "a" | "d";
type Excluded = Exclude<T, U>; // 等价于 "b" | "c"(排除 T 中与 U 重叠的 "a")
2. Extract<T, U>:从 T 中提取可赋值给 U 的类型(与 Exclude 相反)
type Extracted = Extract<T, U>; // 等价于 "a"(提取 T 中与 U 重叠的 "a")
3. ReturnType:获取函数 T 的返回值类型
type Fn = (a: number) => string;
type Return = ReturnType<Fn>; // 等价于 string(Fn 的返回值类型)

// 实战:获取异步函数的返回值类型
type AsyncFn = () => Promise<{ id: number; name: string }>;
type AsyncReturn = ReturnType<AsyncFn>; // 等价于 Promise<{ id: number; name: string }>
4. Parameters:获取函数 T 的参数类型组成的元组
type Fn = (name: string, age: number) => void;
type Params = Parameters<Fn>; // 等价于 [string, number](参数类型元组)

// 实战:复用函数参数类型
function createUser(name: string, age: number) {
  return { name, age };
}
// 获取 createUser 的参数类型,用于其他函数
type CreateUserParams = Parameters<typeof createUser>; // [string, number]

八、类型断言与类型守卫

类型断言和类型守卫用于在“TypeScript 无法自动推断类型”时,手动辅助类型检查,确保类型操作的安全性。

8.1 类型断言(Type Assertion)

类型断言用于“告诉编译器:我知道这个值的类型比你推断的更具体”,语法为 value as Type<Type>value(推荐前者,避免与 JSX 冲突)。

// 场景1:从联合类型中指定更具体的类型
type StringOrNumber = string | number;
let value: StringOrNumber = "hello";
let strLength = (value as string).length; // 断言为 string,安全访问 length

// 场景2:断言为更具体的对象类型
type APIResponse = { data: unknown };
const response: APIResponse = { data: { id: 1, name: "张三" } };
// 断言 data 为具体类型(需确保实际结构匹配,否则运行时可能出错)
const user = response.data as { id: number; name: string };
console.log(user.name);
// 非空断言
const fixed = (name: string | null): string => {
    const postfix = (epithet: string) => {
      // 确保 name 不为 null 或 undefined
      return name!.charAt(0) + '.  the ' + epithet; // ok
    }
    return postfix("great");
}
console.log(fixed("lan")); // l.  the great

注意:类型断言是“编译时”的类型调整,不会影响运行时类型,若断言错误(如将 number 断言为 string),运行时可能抛出错误。

8.2 类型守卫(Type Guard)

类型守卫是返回值为布尔值的函数,通过 parameter is Type 语法告诉编译器:当函数返回 true 时,parameter 的类型为 Type,常用于复杂类型收窄。

// 定义类型守卫:判断 value 是否为 User 类型
type User = { id: number; name: string };
function isUser(value: unknown): value is User {
  // 检查 value 是对象,且包含 id(number)和 name(string)属性
  return (
    typeof value === "object" &&
    value !== null &&
    "id" in value &&
    typeof (value as User).id === "number" &&
    "name" in value &&
    typeof (value as User).name === "string"
  );
}

// 使用类型守卫收窄类型
function handleValue(value: unknown) {
  if (isUser(value)) {
    // 此时 value 自动推断为 User 类型
    console.log(value.name); 
  } else {
    console.log("不是 User 类型");
  }
}

常见的内置类型守卫包括 typeof(检查原始类型)、instanceof(检查类实例)、Array.isArray(检查数组)等。

九、总结

TypeScript 高级类型是构建复杂类型系统的核心,其设计目标是“在保证类型安全的同时,保留 JavaScript 的灵活性”。本文介绍的交叉类型、联合类型、索引类型、映射类型、条件类型等特性,可通过组合实现强大的类型逻辑:

  • 交叉类型用于合并多个类型的约束;
  • 联合类型结合“类型收窄”可处理多状态场景;
  • 索引类型映射类型用于动态操作对象属性的类型;
  • 条件类型支持基于条件选择类型,是类型编程的基础;
  • 类型断言和类型守卫用于辅助编译器处理复杂类型推断。

在实际开发中,高级类型的核心价值在于“用类型描述业务逻辑”,例如通过可区分联合描述状态流转、通过映射类型复用对象结构、通过条件类型提取函数参数等。掌握这些特性,不仅能提升代码的健壮性,还能让类型定义成为“业务文档”的一部分,降低团队协作成本。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

懒羊羊我小弟

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值