You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何从io-ts泛型运行时类型获取ApiResponse<T>静态TS类型?

从io-ts泛型运行时类型推导ApiResponse静态类型的问题

我尝试从io-ts创建的泛型运行时类型中获取ApiResponse<T>静态TypeScript泛型类型,但官方文档无相关说明。

期望的类型对应关系

// 运行时类型
const ApiResponseCodec = <C extends t.Mixed>(codec: C) =>
  t.type({
    code: t.string,
    message: t.union([t.string, t.undefined]),
    result: codec,
  });

// 期望得到的静态类型
type ApiResponse<T> = {
  code: string;
  message: string | undefined;
  result: T;
}

完整代码

import { pipe } from 'fp-ts/lib/function';
import * as E from 'fp-ts/lib/Either';
import * as t from 'io-ts';

const ArticleDTOCodec = t.type({
  id: t.number,
  title: t.string,
});
type ArticleDTO = t.TypeOf<typeof ArticleDTOCodec>;

const PaginationResultCodec = <C extends t.Mixed>(codec: C) =>
  t.type({
    resultList: codec,
    totalItem: t.number,
  });

type PaginationResult<C extends t.Mixed> = t.TypeOf<ReturnType<typeof PaginationResultCodec<C>>>;

const ApiResponseCodec = <C extends t.Mixed>(codec: C) =>
  t.type({
    code: t.string,
    message: t.union([t.string, t.undefined]),
    result: codec,
  });

// 当前写法无法正确推导类型
type ApiResponse<C extends t.Mixed> = t.TypeOf<ReturnType<typeof ApiResponseCodec<C>>>;

const GetArticlesByPageResponseCodec = ApiResponseCodec(PaginationResultCodec(t.array(ArticleDTOCodec)));

export const decodeApiResponse = (res: ApiResponse<PaginationResult<ArticleDTO[]>>) => {
  return pipe(
    res,
    GetArticlesByPageResponseCodec.decode,
    E.fold(
      (e) => 'no',
      (res) => 'yes',
    ),
  );
};

报错信息

Type '{ resultList: unknown; totalItem: number; }' does not satisfy the constraint 'Mixed'.
Type '{ resultList: unknown; totalItem: number; }' is missing the following properties from type 'Type<any, any, unknown>': name, is, validate, encode, and 7 more.ts(2344)

解决方案

问题核心是当前ApiResponse的泛型参数被约束为io-ts运行时类型(t.Mixed),但你实际传入的是静态TypeScript类型,导致类型不匹配。下面提供两种解决方式:

方式一:手动定义静态类型

这是最直接的方案,直接写出你期望的ApiResponse<T>结构,完全和运行时类型解耦:

type ApiResponse<T> = {
  code: string;
  message: string | undefined;
  result: T;
};

同时可以把PaginationResult也改成静态类型定义:

type PaginationResult<T> = {
  resultList: T;
  totalItem: number;
};

方式二:从运行时类型自动推导静态类型

如果你想保持静态类型和运行时类型的一致性,避免手动重复定义,可以调整ApiResponse的泛型参数为静态类型T,通过t.Type<T>关联到对应的运行时类型:

// 保留原运行时类型定义
const ApiResponseCodec = <C extends t.Mixed>(codec: C) =>
  t.type({
    code: t.string,
    message: t.union([t.string, t.undefined]),
    result: codec,
  });

// 修改静态类型定义,泛型参数为静态类型T
type ApiResponse<T> = t.TypeOf<ReturnType<typeof ApiResponseCodec<t.Type<T>>>>;

// 同理修改PaginationResult
const PaginationResultCodec = <C extends t.Mixed>(codec: C) =>
  t.type({
    resultList: codec,
    totalItem: t.number,
  });

type PaginationResult<T> = t.TypeOf<ReturnType<typeof PaginationResultCodec<t.Type<T>>>>;

这种方式会自动从运行时类型推导静态类型,适合需要严格保持类型同步的场景。

修改后,decodeApiResponse的参数类型ApiResponse<PaginationResult<ArticleDTO[]>>就能正常工作,不会再出现类型错误。


内容的提问来源于stack exchange,提问作者Lin Du

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.30 16:30:44