我正在使用 zod 来验证 TypeScript 项目中的表单。 我有一个函数,对于给定的表单模式,可以采用表单值和另一个函数
requestFunc
,该函数将表单值作为输入。
我想让这个函数类型安全,以确保我不会混淆不同的形式。
所以我尝试使用 TypeScript 的 Generics 声明,如下所示:
function schemaTest<T extends z.ZodSchema>(
formData: z.infer<T>,
requestFunc: (formData: z.infer<T>) => void
): void {}
但它没有按预期工作,我永远不会收到任何类型错误,除非我定义两个具有不同命名属性的表单并且我像这样使用该函数:
schemaTest<typeof schemaA>(
schemaA.parse({
email: '[email protected]',
password: '123456'
}),
(formData: z.infer<typeof schemaC>) => {
console.log(formData);
}
);
这是一个测试不同场景的完整示例,我添加了显示类型或错误的注释:
import { z } from 'zod';
// The test function
function schemaTest<T extends z.ZodSchema>(
formData: z.infer<T>,
requestFunc: (formData: z.infer<T>) => void
): void {}
// Example schemas
const schemaA = z.object({
email: z.string().email('Invalid email address'),
password: z.string().min(6, 'Password must be at least 6 characters long')
});
const schemaB = z.object({
email: z.string().email('Invalid email address') // Changed property name
});
const schemaC = z.object({
othername: z.string().email('Invalid email address') // Changed property name
});
// function schemaTest<z.ZodType<any, z.ZodTypeDef, any>>(formData: any, requestFunc: (formData: any) => void): void
schemaTest(
schemaA.parse({
email: '[email protected]',
password: '123456'
}),
(formData: z.infer<typeof schemaB>) => {
console.log(formData);
}
);
// function schemaTest<z.ZodObject<{
// email: z.ZodString;
// password: z.ZodString;
// }, "strip", z.ZodTypeAny, {
// email: string;
// password: string;
// }, {
// email: string;
// password: string;
// }>>(formData: {
// email: string;
// password: string;
// }, requestFunc: (formData: {
// email: string;
// password: string;
// }) => void): void
schemaTest<typeof schemaA>(
schemaA.parse({
email: '[email protected]',
password: '123456'
}),
(formData: z.infer<typeof schemaB>) => {
console.log(formData);
}
);
// Argument of type '(formData: z.infer<typeof schemaC>) => void' is not assignable to parameter of type '(formData: { email: string; password: string; }) => void'.
// Types of parameters 'formData' and 'formData' are incompatible.
// Property 'othername' is missing in type '{ email: string; password: string; }' but required in type '{ othername: string; }'.ts(2345)
schemaTest<typeof schemaA>(
schemaA.parse({
email: '[email protected]',
password: '123456'
}),
(formData: z.infer<typeof schemaC>) => {
console.log(formData);
}
);
// function schemaTest<z.ZodType<any, z.ZodTypeDef, any>>(formData: any, requestFunc: (formData: any) => void): void
schemaTest(
schemaA.parse({
email: '[email protected]',
password: '123456'
}),
(formData: z.infer<typeof schemaC>) => {
console.log(formData);
}
);
问题
这里的问题是 TypeScript 无法根据传递给函数的参数正确推断类型
T
。
原功能实现:
function schemaTest<T extends z.ZodSchema>(
formData: z.infer<T>,
requestFunc: (formData: z.infer<T>) => void
): void {}
z.infer<T>
实用程序类型从 Zod 模式T
中提取推断类型。然而,一旦推断出来,结果类型就不再携带有关 T
本身的任何信息。它变成了具体类型(例如,{ email: string;password: string; }),失去了与原始模式的关联T
。 TypeScript 无法根据推断类型对泛型类型 T
进行逆向工程。
解决方案
您需要让 TypeScript 知道
T
的样子。可以通过两种方式完成:
选项1
将模式本身添加为函数参数,以便 TypeScript 可以从中推断出
T
。
function schemaTest<T extends z.ZodTypeAny>(
schema: T,
formData: z.infer<T>,
requestFunc: (formData: z.infer<T>) => void
): void {}
//Example
schemaTest(
schemaA,
schemaA.parse({
email: '[email protected]',
password: '123456'
}),
(formData) => {
console.log(formData);
}
);
选项2
您可以修改函数以接受携带通用类型信息的高阶函数,而不是将架构作为参数传递。
function schemaTest<T extends z.ZodTypeAny>(
requestFunc: (formData: z.infer<T>) => void
): (formData: z.infer<T>) => void {
return (formData: z.infer<T>) => {
requestFunc(formData);
};
}
//Example
const handleSchemaA = schemaTest<typeof schemaA>((formData) => {
console.log(formData);
});
handleSchemaA(
schemaA.parse({
email: '[email protected]',
password: '123456'
})
);