TypeScript:基于参数类型为React Hook返回数据指定精确类型
我编写了如下React Hook:
export const useForm = <T>(values: T) => { const [formData, setFormData] = useState<FormFieldData<T>>({}); useEffect(() => { const fields = {}; for (const key in values) { const value = values[key]; fields[key] = { value, errorMsg: '', }; } setFormData(fields); }, []); return { formData, }; };
该Hook接收包含字段及其默认值的对象,例如:
const { formData } = useForm({ stringField: '', numberField: 1, booleanField: false, });
它会基于这些键和默认值构建并返回formData对象,结构如下:
{ stringField: { value: '', errorMsg: '', }, numberField: { value: 1, errorMsg: '', }, booleanField: { value: false, errorMsg: '', }, }
为了给formData定义正确的类型,我使用了FormFieldData泛型类型,基于Hook的泛型参数生成formData的类型:
export type FormField<T> = { errorMsg: string; value: T; }; export type FormFieldData<T> = { [K in keyof T]: FormField<T[keyof T]>; };
问题
formData中每个字段对象的value类型变成了传入对象所有类型的联合类型——比如上述示例里,formData.stringField.value的类型为string | number | boolean,但我期望每个字段的value类型与传入Hook时的对应字段原类型一致:formData.stringField.value应为string,formData.numberField.value应为number。
原因
T[keyof T]的含义是:取泛型T所有键对应的类型,合并为联合类型。以上述示例为例,keyof T是"stringField" | "numberField" | "booleanField",T[keyof T]就是T["stringField"] | T["numberField"] | T["booleanField"],也就是string | number | boolean。因此用FormField<T[keyof T]>时,每个字段的value都会被设置成这个联合类型,而非对应键的单独类型。
修复方法
将FormFieldData的定义修改为以下代码,用T[K]替代T[keyof T]:
export type FormFieldData<T> = { [K in keyof T]: FormField<T[K]>; };
这里的K是keyof T中的每个单独键,T[K]就是当前键对应的原类型,这样每个字段的value类型就会和传入时的对应字段类型完全匹配。
内容的提问来源于stack exchange,提问作者BorisAtLast

