如何从io-ts泛型运行时类型获取ApiResponse<T>静态TS类型?
我尝试从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

