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

TypeScript嵌套泛型收窄:如何保障表单Schema中FieldArray的类型安全?

类型安全表单Schema的泛型收窄问题

我正在构建类型安全的表单Schema,其中某一表单项需要对表单类型的子集执行keyof检查,但不知道如何传递并收窄子类型的泛型。具体需求是让FieldArray的fields属性具备与Schema类型一致的类型安全性——当前实现中,fields可以随意添加不符合Form定义的键(比如在errors的fields里加foo、bar、baz不会触发类型警告),但我需要它仅允许包含对应数组元素的合法键(比如rules对应ruleName、isActive、ruleId)。

原实现代码

type FieldType = "text-input" | "number" | "dropdown" | "checkbox";

type Field = {
  label: string;
  type: FieldType;
};

type FieldName<T> = T[keyof T] extends (infer I)[] ? I : never;

type FieldArray<T> = {
  type: "array";
  groupLabel: string;
  fields: Record<keyof FieldName<T> & string, Field>;
};

type SchemaField<T> = Field | FieldArray<T>;

type Schema<T> = Record<keyof T, SchemaField<T>>;

type Form = {
  workflowName: string;
  id: number;
  rules: { ruleName: string; isActive: boolean; ruleId: number }[];
  errors: { errorName: string; isActive: boolean; errorId: number }[];
};

const formSchema: Schema<Form> = {
  workflowName: { type: "text-input", label: "Name" },
  id: { type: "number", label: "Id" },
  rules: {
    type: "array",
    groupLabel: "Rules",
    fields: {
      ruleName: { label: "Rule Name", type: "text-input" },
      isActive: { label: "Is Active", type: "checkbox" },
      ruleId: { label: "Rule Id", type: "number" },
    },
  },
  errors: {
    type: "array",
    groupLabel: "Errors",
    fields: {
      errorName: { label: "Error Name", type: "text-input" },
      isActive: { label: "Is Active", type: "checkbox" },
      errorId: { label: "Error Id", type: "number" },
    },
  },
};

当前未触发警告的错误示例

errors: {
    type: "array",
    groupLabel: "Errors",
    fields: {
      foo: { label: "Error Name", type: "text-input" },
      bar: { label: "Is Active", type: "checkbox" },
      baz: { label: "Error Id", type: "number" },
    },
  },

解决方案

问题核心是原FieldArray的泛型绑定了整个表单类型,导致fields的键范围被扩大为所有数组元素键的联合。需要调整泛型设计,让每个FieldArray绑定对应数组的元素类型:

type FieldType = "text-input" | "number" | "dropdown" | "checkbox";

type Field = {
  label: string;
  type: FieldType;
};

// FieldArray泛型直接接收数组元素类型,严格限制fields的键为元素的key
type FieldArray<Item> = {
  type: "array";
  groupLabel: string;
  fields: Record<keyof Item & string, Field>;
};

// 根据表单字段类型自动匹配SchemaField:数组类型用FieldArray,其他用Field
type SchemaField<Value> = 
  Value extends (infer Item)[] 
    ? FieldArray<Item> 
    : Field;

// 遍历表单字段,为每个字段生成对应类型的SchemaField
type Schema<Form> = {
  [K in keyof Form]: SchemaField<Form[K]>;
};

type Form = {
  workflowName: string;
  id: number;
  rules: { ruleName: string; isActive: boolean; ruleId: number }[];
  errors: { errorName: string; isActive: boolean; errorId: number }[];
};

const formSchema: Schema<Form> = {
  workflowName: { type: "text-input", label: "Name" },
  id: { type: "number", label: "Id" },
  rules: {
    type: "array",
    groupLabel: "Rules",
    fields: {
      ruleName: { label: "Rule Name", type: "text-input" },
      isActive: { label: "Is Active", type: "checkbox" },
      ruleId: { label: "Rule Id", type: "number" },
    },
  },
  errors: {
    type: "array",
    groupLabel: "Errors",
    fields: {
      errorName: { label: "Error Name", type: "text-input" },
      isActive: { label: "Is Active", type: "checkbox" },
      errorId: { label: "Error Id", type: "number" },
      // foo: { label: "Test", type: "text-input" } // 此处会触发类型错误,符合预期
    },
  },
};

关键调整说明

  1. FieldArray泛型优化:将泛型参数改为数组元素类型,确保fields的键严格匹配当前数组元素的属性名。
  2. 条件类型自动匹配:SchemaField通过条件判断表单字段是否为数组,自动返回对应的数组型或普通型字段定义。
  3. 映射类型遍历表单:Schema使用映射类型遍历表单的每个字段,为每个字段生成精准匹配的Schema定义,避免泛型范围过大导致的类型宽松。

修改后,添加非法键会立即触发TypeScript类型警告,完全符合类型安全的需求。

内容的提问来源于stack exchange,提问作者Scott Z

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.03 05:21:57