ts在运行时校验数据类型的探索

在 TypeScript 中,校验接口返回的数据是否符合定义的类型,通常需要结合运行时检查和 TypeScript 的静态类型系统。TypeScript 的类型检查仅在编译时生效,而接口返回的数据是运行时获取的,因此需要额外的工具或方法来确保数据符合预期类型。


直接使用类型断言告诉 TypeScript 你相信接口返回的数据符合定义的类型。这种方法不进行运行时校验,仅依赖开发者的假设。

typescript 复制代码
interface User {
  id: number;
  name: string;
}

async function fetchUser(): Promise<User> {
  const response = await fetch('/api/user');
  const data = await response.json() as User; // 类型断言
  return data;
}

为了在运行时校验数据类型,手动编写校验逻辑,在运行时检查返回数据的结构和类型是否符合 TypeScript 定义。这种方法需要每次手写校验函数,不实用。

typescript 复制代码
interface User {
  id: number;
  name: string;
}

function isUser(data: unknown): data is User {
  if (typeof data !== 'object' || data === null) return false;
  const user = data as User;
  return (
    typeof user.id === 'number' &&
    typeof user.name === 'string'
  );
}

async function fetchUser(): Promise<User> {
  const response = await fetch('/api/user');
  const data = await response.json();
  
  if (!isUser(data)) {
    throw new Error('返回的数据不符合 User 类型');
  }
  
  return data;
}

fetchUser()
  .then(user => console.log(user.id, user.name))
  .catch(err => console.error(err));

使用第三方库(如 zodio-ts

借助成熟的运行时类型校验库,可以更优雅地定义和校验接口返回的数据。这些库同时支持 TypeScript 类型推导和运行时验证。

使用 zod

zod 是一个流行的 TypeScript 运行时类型校验库,支持类型定义和校验。

  1. 安装

    bash 复制代码
    npm install zod
  2. 定义 Schema 并校验

    typescript 复制代码
    import { z } from 'zod';
    
    // 定义 Schema,同时生成   TypeScript 类型会自动推导
    const UserSchema = z.object({
      id: z.number(),
      name: z.string(),
    });
    
    // TypeScript 类型
    type User = z.infer<typeof UserSchema>;
    
    async function fetchUser(): Promise<User> {
      const response = await fetch('/api/user');
      const data = await response.json();
      
      // 校验数据
      return UserSchema.parse(data); // 如果不符合 Schema,会抛出错误
    }
    
    fetchUser()
      .then(user => console.log(user.id, user.name))
      .catch(err => console.error('校验失败:', err));
  • 优点
    • 类型安全:z.infer 自动生成 TypeScript 类型。
    • 强大的校验功能:支持嵌套对象、数组、可选字段等。
    • 错误信息详细,易于调试。

使用 io-ts

io-ts 是另一个专注于 TypeScript 的运行时类型校验库。

  1. 安装

    bash 复制代码
    npm install io-ts fp-ts
  2. 定义和校验

    typescript 复制代码
    import * as t from 'io-ts';
    import { either } from 'fp-ts/Either';
    
    // 定义类型
    const User = t.type({
      id: t.number,
      name: t.string,
    });
    
    // TypeScript 类型
    type User = t.TypeOf<typeof User>;
    
    async function fetchUser(): Promise<User> {
      const response = await fetch('/api/user');
      const data = await response.json();
      
      const result = User.decode(data);
      if (either.isLeft(result)) {
        throw new Error('校验失败: ' + JSON.stringify(result.left));
      }
      
      return result.right;
    }
  • 优点:函数式编程风格,类型推导强大。
  • 缺点 :学习曲线稍陡,依赖 fp-ts
  • 适用场景 :喜欢函数式编程或已有 fp-ts 的项目。

方法 4:结合 JSON Schema

如果接口有标准的 JSON Schema 定义,可以使用工具(如 ajv)进行校验。

  1. 安装

    bash 复制代码
    npm install ajv
  2. 定义和校验

    typescript 复制代码
    import Ajv from 'ajv';
    
    const ajv = new Ajv();
    
    const userSchema = {
      type: 'object',
      properties: {
        id: { type: 'number' },
        name: { type: 'string' },
      },
      required: ['id', 'name'],
      additionalProperties: false,
    };
    
    interface User {
      id: number;
      name: string;
    }
    
    const validate = ajv.compile(userSchema);
    
    async function fetchUser(): Promise<User> {
      const response = await fetch('/api/user');
      const data = await response.json();
      
      if (!validate(data)) {
        throw new Error('校验失败: ' + JSON.stringify(validate.errors));
      }
      
      return data as User;
    }
  • 优点:与 JSON Schema 标准兼容,适合跨语言项目。
  • 缺点:需要手动维护 TypeScript 类型和 Schema 的一致性。
  • 适用场景:已有 JSON Schema 定义的项目。

最佳实践

  1. 选择合适的工具

    • 小型项目:手动校验或类型断言。
    • 中大型项目:推荐 zod,简单易用且功能强大。
    • 复杂校验:io-tsajv
  2. 错误处理

    • 捕获校验错误并提供用户友好的提示。
    • 记录校验失败的日志,便于调试。
  3. 与后端协作

    • 与后端约定数据格式,减少校验负担。
    • 如果可能,使用 OpenAPI/Swagger 生成类型和校验。
  4. 性能考虑

    • 对于频繁调用的接口,避免过于复杂的校验逻辑。

示例:综合使用 zod

typescript 复制代码
import { z } from 'zod';

const UserSchema = z.object({
  id: z.number().positive(),
  name: z.string().min(1),
  email: z.string().email().optional(),
});

type User = z.infer<typeof UserSchema>;

async function fetchUser(id: number): Promise<User> {
  try {
    const response = await fetch(`/api/user/${id}`);
    if (!response.ok) throw new Error('网络错误');
    const data = await response.json();
    return UserSchema.parse(data);
  } catch (error) {
    if (error instanceof z.ZodError) {
      console.error('数据校验失败:', error.errors);
      throw new Error('无效的用户数据');
    }
    throw error;
  }
}

fetchUser(1)
  .then(user => console.log('用户:', user))
  .catch(err => console.error('错误:', err));

总结

  • 第三方库 (如 zod):推荐,兼顾类型安全和运行时校验。
  • JSON Schema:适合标准化项目。
相关推荐
徐同保1 分钟前
通过AzureOpenAI请求gpt-4.1-mini
前端
红尘散仙12 分钟前
四、WebGPU 基础入门——Uniform 缓冲区与内存对齐
前端·rust·gpu
进取星辰23 分钟前
13、性能优化:魔法的流畅之道——React 19 memo/lazy
前端·react.js·前端框架
zwjapple29 分钟前
React中createPortal 的详细用法
前端·javascript·react.js
小矮马30 分钟前
React-组件通信
前端·javascript·react.js
codingandsleeping39 分钟前
pnpm + monorepo:高效的项目管理方式
前端
程序员三千_1 小时前
最近爆火的MCP到底是什么?
前端
古时的风筝1 小时前
暴论:2025年,程序员必学技能就是MCP
前端·后端·mcp
古时的风筝1 小时前
这编程圈子变化太快了,谁能告诉我 MCP 是什么
前端·后端·mcp
王月lydia1 小时前
环境变量篇-vue3的H5项目从0到1工程化落地经验篇2
前端