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:适合标准化项目。
相关推荐
朴拙数科25 分钟前
技术长期主义:用本分思维重构JavaScript逆向知识体系(一)Babel、AST、ES6+、ES5、浏览器环境、Node.js环境的关系和处理流程
javascript·重构·es6
拉不动的猪1 小时前
vue与react的简单问答
前端·javascript·面试
污斑兔1 小时前
如何在CSS中创建从左上角到右下角的渐变边框
前端
星空寻流年1 小时前
css之定位学习
前端·css·学习
旭久2 小时前
react+antd封装一个可回车自定义option的select并且与某些内容相互禁用
前端·javascript·react.js
是纽扣也是烤奶2 小时前
关于React Redux
前端
阿丽塔~2 小时前
React 函数组件间怎么进行通信?
前端·javascript·react.js
冴羽3 小时前
SvelteKit 最新中文文档教程(17)—— 仅服务端模块和快照
前端·javascript·svelte
uhakadotcom3 小时前
Langflow:打造AI应用的强大工具
前端·面试·github