我正在使用 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 :
我定义了一个类型保护来检查表单的提交是否与预期结构匹配,并将该类型保护添加到 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[]
有办法完成我在这里想做的事情吗?
我的经验法则 - 不要使用数组作为泛型参数,通常数组元素类型和其他泛型参数之间的链接被破坏,而是使用数组元素类型作为泛型参数(更深层次)。
这样 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[];