Skip to content

第 6 章:现代 TypeScript 趋势 —— type 的崛起

在之前的章节中,我们探讨了在不同场景下如何选择 interfacetype。近年来,随着 TypeScript 生态的发展和函数式编程理念的普及,我们看到了一个明显的趋势:type 正在变得越来越重要,甚至可以说正在崛起。

Zod、io-ts 推动函数式类型编程

现代类型验证库如 Zod 和 io-ts 都采用了函数式的方法,大量使用 type 来定义模式:

typescript
import { z } from 'zod';

// 使用 Zod 定义用户模式
const UserSchema = z.object({
  id: z.number(),
  name: z.string().min(1),
  email: z.string().email(),
  roles: z.array(z.enum(['admin', 'user', 'guest'])).default(['user'])
});

// Zod 自动推断出类型
type User = z.infer<typeof UserSchema>;

// 使用示例
const userData = {
  id: 1,
  name: "Alice",
  email: "alice@example.com",
  roles: ["admin"]
};

const result = UserSchema.safeParse(userData);
if (result.success) {
  // result.data 的类型是 User
  console.log(result.data.name);
} else {
  console.error(result.error);
}

这种模式将类型定义和运行时验证结合在一起,提供了端到端的类型安全。

satisfies 操作符让 type 更安全

TypeScript 4.9 引入的 satisfies 操作符大大增强了 type 的能力:

typescript
// 定义组件配置
type ComponentConfig = {
  name: string;
  props: Record<string, any>;
  render: (props: any) => string;
};

// 使用 satisfies 确保对象符合类型,同时保留具体信息
const buttonConfig = {
  name: 'Button',
  props: {
    variant: 'primary',
    size: 'medium'
  },
  render: (props) => `<button class="${props.variant}">${props.children}</button>`
} satisfies ComponentConfig;

// 现在我们可以访问具体的 props 属性
console.log(buttonConfig.props.variant); // 类型安全的访问

// 如果不符合类型会报错
const invalidConfig = {
  name: 'Button',
  props: {
    variant: 'primary'
  },
  render: () => '' // 缺少参数
} satisfies ComponentConfig; // 编译错误

satisfies 操作符既保证了类型安全,又保留了字面量类型的具体信息,这是传统 interface 难以做到的。

构建时类型检查取代运行时 instanceof

现代应用越来越倾向于在构建时完成类型检查,而不是依赖运行时的 instanceof 检查:

typescript
// 传统的 instanceof 检查
class ApiError extends Error {
  constructor(public statusCode: number, message: string) {
    super(message);
  }
}

function handleError(error: unknown) {
  if (error instanceof ApiError) {
    // 处理 API 错误
    console.log(`API Error ${error.statusCode}: ${error.message}`);
  } else if (error instanceof Error) {
    // 处理一般错误
    console.log(`Error: ${error.message}`);
  }
}

// 现代的基于标签的类型守卫
type ApiError = {
  _tag: 'ApiError';
  statusCode: number;
  message: string;
};

type NetworkError = {
  _tag: 'NetworkError';
  message: string;
};

type AppError = ApiError | NetworkError;

function isApiError(error: AppError): error is ApiError {
  return error._tag === 'ApiError';
}

function handleModernError(error: AppError) {
  if (isApiError(error)) {
    console.log(`API Error ${error.statusCode}: ${error.message}`);
  } else {
    console.log(`Network Error: ${error.message}`);
  }
}

这种模式不仅在编译时提供了类型安全,而且在运行时也更加高效,因为不需要遍历原型链进行检查。

未来:type 成为主流

观察现代 TypeScript 代码库,我们可以看到以下几个趋势:

  1. React 生态:几乎所有现代 React 项目都倾向于使用 type 而不是 interface 来定义组件 props
  2. 函数式工具库:像 fp-ts、Ramda 等函数式编程库大量使用 type 和泛型
  3. 类型验证库:Zod、Yup、Joi 等库都采用基于 type 的 API 设计
  4. 状态管理:Redux Toolkit、Zustand 等现代状态管理库鼓励使用 type 定义状态结构
typescript
// Redux Toolkit 使用 type 定义状态
import { createSlice, PayloadAction } from '@reduxjs/toolkit';

type Todo = {
  id: number;
  text: string;
  completed: boolean;
};

type TodoState = {
  todos: Todo[];
  filter: 'all' | 'active' | 'completed';
};

const initialState: TodoState = {
  todos: [],
  filter: 'all'
};

const todoSlice = createSlice({
  name: 'todos',
  initialState,
  reducers: {
    addTodo: (state, action: PayloadAction<string>) => {
      const newTodo: Todo = {
        id: Date.now(),
        text: action.payload,
        completed: false
      };
      state.todos.push(newTodo);
    }
  }
});

小结

在本章中,我们探讨了现代 TypeScript 发展中的几个重要趋势:

  1. Zod、io-ts 等类型验证库推动了函数式类型编程
  2. satisfies 操作符让 type 更加强大和安全
  3. 构建时类型检查逐渐取代运行时 instanceof 检查
  4. type 正在成为现代 TypeScript 开发的主流选择

这些趋势表明,TypeScript 正在从传统的面向对象类型系统向更加现代化的、函数式的类型系统演进。

下一章,我们将探讨一些常见的反模式,帮助你避免在使用 typeinterface 时犯常见的错误。


思考题: 你在项目中有没有使用过 Zod 或类似的类型验证库?它们给你带来了哪些好处?