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

TypeScript:如何强制约束继承Record的泛型类型保持一致性

解决TypeScript泛型FormErrors的类型约束问题

问题背景

我编写了一个用于处理验证逻辑的泛型类型FormErrors,用来生成对应数据类型的错误结构:

定义基础数据类型:

type User = { name: string; cars: Car[] }
type Car = { name: string }

错误结构规则:

  • 若属性存在错误,用字符串数组定义错误信息,错误对象仅包含有错误的属性,无错误的属性不会出现:
    errors = { name: ["is required"] }
    
  • 当错误存在于嵌套数组对象中时,生成对应关联对象的错误数组,内部属性遵循相同结构:
    errors = { cars: [{name: ["is required"]}] }
    

现有代码

当前的FormErrors类型定义如下:

type FormErrors<T extends Record<string, unknown>> = {
  [K in keyof Partial<T>]: T[K] extends Record<string, unknown>[] ? Array<FormErrors<T[K][number]>> : Array<string>;
};

使用示例:

let errors: FormErrors<User>;

errors.name // string[] | undefined
errors.cars // FormErrors<Car>[] | undefined

问题描述

当前TypeScript并未强制约束该泛型的类型参数。例如,一个声明接收FormErrors<User>的方法,会允许传入FormErrors<Car>而无任何类型警告。这是因为二者的可选属性集合存在重叠(都包含name属性),TypeScript的结构类型系统会认为它们兼容。

解决方案

要解决这个问题,我们可以给FormErrors添加一个唯一的类型标记属性,让不同类型参数的FormErrors在结构上产生差异,从而被TypeScript视为不同的类型。

方案1:使用唯一符号标记

// 声明一个唯一符号,用于标记FormErrors的类型参数
declare const formErrorsTag: unique symbol;

type FormErrors<T extends Record<string, unknown>> = {
  // 保留原有的错误属性映射,将Partial<T>改为keyof T的可选属性(语义更准确)
  [K in keyof T]?: T[K] extends Record<string, unknown>[] 
    ? Array<FormErrors<T[K][number]>> 
    : Array<string>;
} & {
  // 添加标记属性,其类型为原类型T,确保不同T的FormErrors不兼容
  [formErrorsTag]: T;
};

方案2:使用字符串字面量标记(简化版)

如果不需要严格的类型参数关联,也可以使用带有类型参数标识的字符串字面量:

type FormErrors<T extends Record<string, unknown>> = {
  [K in keyof T]?: T[K] extends Record<string, unknown>[] 
    ? Array<FormErrors<T[K][number]>> 
    : Array<string>;
} & {
  // 添加只读的字符串标记,区分不同类型参数的FormErrors
  readonly [Symbol.toStringTag]: `FormErrors<${string}>`;
};

效果验证

修改后,当尝试将FormErrors<Car>传入预期FormErrors<User>的方法时,TypeScript会抛出类型不兼容的错误,实现了我们需要的强制约束。

内容的提问来源于stack exchange,提问作者James St-Pierre

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 23:33:19