Skip to content

TypeScript 高级类型使用指南

TypeScript 的类型系统非常强大,掌握高级类型能够显著提升代码的健壮性和开发体验。本文将深入探讨 TypeScript 的高级类型,并通过实例讲解它们的应用场景。

基础类型回顾

在深入高级类型之前,让我们先回顾一下 TypeScript 的基础类型:

typescript
// 基本类型
const isActive: boolean = true;
const count: number = 42;
const username: string = 'Charon';

// 数组
const numbers: number[] = [1, 2, 3];
const names: Array<string> = ['Alice', 'Bob'];

// 元组
const point: [number, number] = [10, 20];

// 枚举
enum Direction {
  Up,
  Down,
  Left,
  Right
}
const move: Direction = Direction.Up;

// Any, Unknown, Void, Null, Undefined
const anything: any = 'could be anything';
const something: unknown = 'type-safe any';
const noReturn: void = undefined;
const empty: null = null;
const notAssigned: undefined = undefined;

联合类型与交叉类型

联合类型和交叉类型是 TypeScript 中最常用的高级类型组合方式。

联合类型(Union Types)

联合类型表示一个值可以是几种类型之一:

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

function printId(id: ID) {
  console.log(id);
  
  // 类型收窄
  if (typeof id === 'string') {
    console.log(id.toUpperCase());
  } else {
    console.log(id.toFixed(2));
  }
}

printId("abc123");  // 可以是字符串
printId(123456);    // 也可以是数字

交叉类型(Intersection Types)

交叉类型将多个类型合并为一个类型,包含了所需的所有类型的特性:

typescript
// 定义两个接口
interface HasName {
  name: string;
}

interface HasAge {
  age: number;
}

// 使用交叉类型组合接口
type Person = HasName & HasAge;

// 必须同时包含两个接口的所有属性
const person: Person = {
  name: "Alice",
  age: 30
};

类型别名与接口

TypeScript 提供了接口和类型别名两种方式来定义对象类型,它们有细微的差别。

类型别名(Type Aliases)

类型别名用 type 关键字定义,可以为任何类型提供新名称:

typescript
// 简单的类型别名
type Point = {
  x: number;
  y: number;
};

// 可以引用自身(递归类型)
type Tree<T> = {
  value: T;
  children?: Tree<T>[];
};

// 联合类型的别名
type Status = "pending" | "fulfilled" | "rejected";

const result: Status = "fulfilled";

接口(Interfaces)

接口用 interface 关键字定义,用于描述对象的形状:

typescript
// 基本接口
interface User {
  id: number;
  name: string;
}

// 扩展接口
interface Employee extends User {
  department: string;
}

const employee: Employee = {
  id: 1,
  name: "John",
  department: "Engineering"
};

// 声明合并(接口的独特特性)
interface Window {
  title: string;
}

interface Window {
  ts: TypeScriptAPI;
}

// 两个接口声明会合并
const src: Window = { title: "TypeScript", ts: {} };

条件类型

条件类型是 TypeScript 中极其强大的特性,它使类型系统具备了逻辑判断能力。

typescript
// 基本条件类型语法:T extends U ? X : Y
type IsString<T> = T extends string ? true : false;

type Result1 = IsString<"hello">;  // true
type Result2 = IsString<42>;       // false

// 条件类型中的推断(infer)
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;

function greet(): string {
  return "Hello!";
}

type GreetReturn = ReturnType<typeof greet>;  // string

映射类型

映射类型可以从现有类型创建新类型,通过遍历现有类型的属性来转换或修改它们。

typescript
// 基本映射类型
type Readonly<T> = {
  readonly [P in keyof T]: T[P];
};

interface Todo {
  title: string;
  description: string;
}

// 创建一个所有属性都为只读的新类型
const todo: Readonly<Todo> = {
  title: "Learn TypeScript",
  description: "Study advanced types"
};

// todo.title = "New Title";  // Error: Cannot assign to 'title' because it is a read-only property

// Partial 映射类型,使所有属性可选
type Partial<T> = {
  [P in keyof T]?: T[P];
};

// 部分更新对象
function updateTodo(todo: Todo, fieldsToUpdate: Partial<Todo>) {
  return { ...todo, ...fieldsToUpdate };
}

const updatedTodo = updateTodo(todo, {
  description: "Focus on mapped types"
});

工具类型

TypeScript 内置了许多实用的工具类型,它们都是通过类型操作而非运行时操作来实现的。

typescript
// 内置工具类型示例
interface Person {
  name: string;
  age: number;
  address: {
    city: string;
    country: string;
  };
}

// Pick:从类型中选择部分属性
type PersonName = Pick<Person, 'name'>;  // { name: string }

// Omit:排除某些属性
type PersonWithoutAddress = Omit<Person, 'address'>;  // { name: string; age: number }

// Exclude:从联合类型中排除某些类型
type Primitive = string | number | boolean;
type StringOrNumber = Exclude<Primitive, boolean>;  // string | number

// Extract:提取符合条件的类型
type StringOnly = Extract<Primitive, string>;  // string

// Required:使所有属性必选
type RequiredPerson = Required<Partial<Person>>;  // 与 Person 相同

// Record:创建具有特定类型属性的类型
type CountryInfo = Record<string, { population: number }>;
const countries: CountryInfo = {
  China: { population: 1400000000 },
  US: { population: 330000000 }
};

实际应用案例

API 响应类型

使用条件类型和泛型定义 API 响应类型:

typescript
// API响应类型
interface ApiResponse<T> {
  data: T;
  status: number;
  message: string;
  timestamp: string;
}

// 不同实体类型
interface User {
  id: number;
  username: string;
}

interface Post {
  id: number;
  title: string;
  content: string;
}

// 使用泛型创建特定响应类型
type UserResponse = ApiResponse<User>;
type PostResponse = ApiResponse<Post>;

// 模拟API调用
async function fetchUser(id: number): Promise<UserResponse> {
  // 实际应用中这里会调用API
  return {
    data: { id, username: "user" + id },
    status: 200,
    message: "Success",
    timestamp: new Date().toISOString()
  };
}

React 组件 Props 类型

在 React 项目中使用 TypeScript 定义组件 Props:

typescript
// 基础属性
interface BaseProps {
  className?: string;
  style?: React.CSSProperties;
}

// 按钮属性
interface ButtonProps extends BaseProps {
  type: 'primary' | 'secondary' | 'danger';
  size?: 'small' | 'medium' | 'large';
  onClick: () => void;
  disabled?: boolean;
  children: React.ReactNode;
}

// 定义组件
function Button({ 
  type, 
  size = 'medium', 
  onClick, 
  disabled, 
  children,
  ...rest
}: ButtonProps) {
  return (
    <button
      className={`btn btn-${type} btn-${size}`}
      onClick={onClick}
      disabled={disabled}
      {...rest}
    >
      {children}
    </button>
  );
}

高级类型编程

TypeScript 的类型系统本身就是一种编程语言,可以用来编写复杂的类型。

递归类型

定义具有无限嵌套能力的类型:

typescript
// 递归定义JSON值类型
type JSONValue = 
  | string
  | number
  | boolean
  | null
  | JSONValue[]
  | { [key: string]: JSONValue };

// 定义嵌套对象
const data: JSONValue = {
  name: "TypeScript",
  version: 4.5,
  features: ["templates", "types"],
  metadata: {
    releaseDate: "2021-11-17",
    isStable: true,
    supporters: [
      { name: "Microsoft", level: 1 }
    ]
  }
};

字符串操作类型

使用模板字面类型操作字符串:

typescript
// 字符串操作
type Greeting = `Hello, ${string}!`;

// 有效的值
const g1: Greeting = "Hello, World!";
const g2: Greeting = "Hello, TypeScript!";

// 无效的值
// const g3: Greeting = "Hi, World!";  // Error

// 模板字面类型结合联合类型
type EventName = 'click' | 'focus' | 'blur';
type EventHandler = `on${Capitalize<EventName>}`;  // 'onClick' | 'onFocus' | 'onBlur'

// 路径生成类型
type NestedPaths<T, P extends string = ''> = 
  T extends object 
    ? { [K in keyof T]: K extends string 
        ? P extends '' 
          ? NestedPaths<T[K], K> 
          : NestedPaths<T[K], `${P}.${K}`>
        : never
    }[keyof T] | P
    : P;

// 生成所有可能的嵌套对象路径
interface User {
  name: string;
  address: {
    street: string;
    city: string;
    geo: {
      lat: number;
      lng: number;
    }
  }
}

type UserPaths = NestedPaths<User>;
// 'name' | 'address' | 'address.street' | 'address.city' | 'address.geo' | 'address.geo.lat' | 'address.geo.lng'

类型安全的实战技巧

类型守卫

使用类型守卫进行运行时类型检查:

typescript
// 用户定义的类型守卫
function isString(value: unknown): value is string {
  return typeof value === 'string';
}

function processValue(value: unknown) {
  if (isString(value)) {
    // 在这个作用域中,TypeScript 知道 value 是字符串
    console.log(value.toUpperCase());
  } else {
    console.log("Not a string:", value);
  }
}

// instanceof 类型守卫
class ApiError extends Error {
  statusCode: number = 500;
}

class ValidationError extends Error {
  validationErrors: string[] = [];
}

function handleError(error: Error) {
  if (error instanceof ApiError) {
    // 处理API错误
    console.error(`API Error ${error.statusCode}: ${error.message}`);
  } else if (error instanceof ValidationError) {
    // 处理验证错误
    console.error(`Validation Errors: ${error.validationErrors.join(', ')}`);
  } else {
    // 处理其他错误
    console.error(`Unknown Error: ${error.message}`);
  }
}

严格的空值检查

TypeScript 的严格空检查可以避免常见的空引用错误:

typescript
// 开启严格空检查时,可选参数和可选属性类型会自动包含 undefined
function printName(name?: string) {
  // 错误: 'name' 可能为 undefined
  // console.log(name.toUpperCase());
  
  // 正确: 先检查是否存在
  if (name) {
    console.log(name.toUpperCase());
  } else {
    console.log("No name provided");
  }
}

// 非空断言操作符(当你确定某个值不可能为空时使用)
function getNonNullName(name: string | null) {
  // 使用 ! 告诉 TypeScript 该值不为 null 或 undefined
  return name!.toUpperCase();
}

结论

TypeScript 的高级类型系统使我们能够构建更加健壮的应用程序,通过类型定义捕获潜在错误。掌握这些高级类型特性,你将能够:

  1. 创建更精确的类型定义,提高代码质量
  2. 减少运行时错误,增强代码稳定性
  3. 提供更好的开发体验和自动补全功能
  4. 使重构更加安全可靠

随着对 TypeScript 类型系统的深入理解,你会发现类型本身就是一种强大的抽象和文档工具,它可以清晰地表达代码的意图和约束,使团队合作更加高效。

在下一篇文章中,我们将探讨如何进行更复杂的 TypeScript 类型体操练习,挑战你的类型系统理解能力!