如何根据表单结构动态检查并推断表单提交类型?

问题描述 投票:0回答:1

我正在使用 TypeScript 开发表单验证功能,其中表单由多个部分组成,每个部分都有一个特定的答案类型(例如字符串、数字或数组)。我想动态检查表单提交是否与预期结构匹配,然后推断提交数据的正确类型以在函数的其余部分中使用。

结构示例:

type Section = 'name' | 'age' | 'nicknames';

type Form = readonly Section[];

// Map sections to their answer types
type AnswerForSection<Section> = Section extends 'age'
  ? number
  : Section extends 'name'
  ? string
  : Section extends 'nicknames'
  ? string[]
  : never;

// Expected submission shape for a form
type ValidFormSubmission<F extends Form> = {
  [Section in F[number]]: AnswerForSection<Section>;
};


// Examples of forms
const form1 = ['name', 'age'] as const;
const form2 = ['name', 'nicknames'] as const;

type Form1SubmissionType = ValidFormSubmission<typeof form1>;
// { name: string, age: number } - This is what I expect

我想编写一个函数 checkFormAnswers :

  • 接受表格并提交。
  • 首先检查提交的内容是否符合表单的预期形状。
  • 如果检查通过,它应该让 TypeScript 推断表单提交的类型(例如,年龄字段应推断为数字)。

我定义了一个类型保护来检查表单的提交是否与预期结构匹配,并将该类型保护添加到 checkFormAnswers 函数的顶部:

function satisfiesForm<F extends Form>(
  form: F,
  submissionData: Record<string, any>,
): submissionData is ValidFormSubmission<F> {
  * Implementation details not important for this question *
}

function checkFormAnswers<F extends Form>(
  form: F,
  submission: Record<string, any>,
) {
  if (satisfiesForm(form, submission)) {
    form.forEach((question) => {
      if (question === 'age') {
      // I want TS to infer that submission has an 'age' field because the form does
      // and the submission satisfies the form
        const age = submission['age']; 
        console.log('Age is:', age); // I want TS to infer that age is a number
      }
    });
  } else {
    console.error('Invalid form submission');
  }
}

但是 Typescript 似乎无法在守卫之后正确推断类型。我收到以下 ts 错误:

Element implicitly has an 'any' type because expression of type '"age"' can't be used to index type 'ValidFormSubmission<F>'. Property 'age' does not exist on type 'ValidFormSubmission<F>'.

我可以通过将 checkFormAnswers 编辑为以下内容来消除此错误:

function checkFormAnswers<F extends Form>(
  form: F,
  submission: Record<string, any>,
) {
  if (satisfiesForm(form, submission)) {
    form.forEach((question) => {
      const key = question as F[number];
      if (key === 'age') {
        const age = submission[key];
        console.log('Age is:', age);
      }
    });
  } else {
    console.error('Invalid form submission');
  }
}

但是年龄被推断为

string | number | string[]

有办法完成我在这里想做的事情吗?

typescript typescript-generics type-inference
1个回答
0
投票

我的经验法则 - 不要使用数组作为泛型参数,通常数组元素类型和其他泛型参数之间的链接被破坏,而是使用数组元素类型作为泛型参数(更深层次)。

游乐场

这样 TS 就知道

   const age = submission[question]; 

question
是有效密钥。

type Section = 'name' | 'age' | 'nicknames';

// Map sections to their answer types
type AnswerForSection<Section> = Section extends 'age'
  ? number
  : Section extends 'name'
  ? string
  : Section extends 'nicknames'
  ? string[]
  : never;

// Expected submission shape for a form
type ValidFormSubmission<S extends Section> = {
  [Section in S]: AnswerForSection<Section>;
};


// Examples of forms
const form1 = ['name', 'age'] as const;
const form2 = ['name', 'nicknames'] as const;

type Form1SubmissionType = ValidFormSubmission<typeof form1[number]>;
// { name: string, age: number } - This is what I expect

declare function satisfiesForm<S extends Section>(
  form: S[],
  submissionData: Record<string, any>,
): submissionData is ValidFormSubmission<S>

function checkFormAnswers<S extends Section>(
  form: S[],
  submission: Record<string, any>,
) {
  if (satisfiesForm(form, submission)) {
    form.forEach((question) => {
      if (question === 'age') {
      // I want TS to infer that submission has an 'age' field because the form does
      // and the submission satisfies the form
        const age = submission[question]; 
        console.log('Age is:', age); // I want TS to infer that age is a number
      }
    });
  } else {
    console.error('Invalid form submission');
  }
}

我没有费心在这里创建

Form
类型,但你可以

type Form<S> = S[];
© www.soinside.com 2019 - 2024. All rights reserved.