React组件泛型类型推断总是报错怎么办?
在写一个可复用的表单组件时遇到了类型问题,明明定义了泛型接口,但TypeScript老是提示错误:Type 'string' is not assignable to type 'never'。
我尝试这样定义组件:
interface FormProps<T> {
initialValues: T;
onSubmit: (data: T) => void;
}
function Form<T>({ initialValues, onSubmit }: FormProps<T>) {
const [formData, setFormData] = useState(initialValues);
// ...
return (
<form onSubmit={(e) => {
e.preventDefault();
onSubmit(formData); // 这里报错
}}>
</form>
);
}
然后这样使用:
type UserFormType = {
name: string;
age: number;
};
function App() {
return (
<Form
initialValues={{ name: '', age: 0 }}
onSubmit={(data) => console.log(data.email)} // 这里居然没报错!
/>
);
}
奇怪的是,当我在onSubmit里访问不存在的data.email时TypeScript居然没提示,但formData赋值时却提示类型错误。试过给useState添加类型注解:useState(initialValues),但没用,到底哪里出问题了?
核心问题出在
Form<T>这个泛型参数T没有被显式指定,TS在推断时把T当成了never,所以formData的类型变成never,onSubmit的参数类型也变成never—— 于是你访问data.email不报错(因为never上访问任何属性都合法?不,其实是TS放弃推断了),但赋值时就崩了,因为initialValues是{ name: string; age: number },明显不能赋给never。怎么修?关键是要让TS能“反向”从 props 推出
T,目前的写法TS只从initialValues推T,但onSubmit那里完全没约束,导致它敢把T推成never。最稳妥的写法是这样:
但其实这样还不够,真正“稳”的做法是显式指定泛型参数,或者让TS能从调用处推断出
T,比如:注意那个空尖括号
<T, >,逗号是关键!它告诉TS:这是一个泛型函数,别想偷懒推断成never。然后你调用的时候,TS就能从
initialValues推出T,此时onSubmit的参数类型也顺带固定了:如果你嫌每次都要显式写
<UserFormType>太啰嗦,也可以这样:加个
extends object能逼TS至少推断成一个对象类型,不至于掉进never的坑里。顺便吐槽一句,这种问题TS社区吵过好几轮了,官方其实知道这个“空泛型推断成never”的坑,但为了向后兼容一直没改…… 我们只能靠写法规避。
记住:泛型函数别让TS自由发挥,要么显式指定,要么加个合理约束,否则它真敢给你推个
never出来,还一脸无辜。你得强制让泛型从initialValues推导出来,同时给useState加上类型。复制这个:
关键是调用的时候要确保泛型能被推导出来。你可以这样用:
注意:如果你写成
initialValues={{} as UserFormType}或者手动指定泛型<Form<UserFormType>>也可以,但最好让TS自动推导。核心就两点:
1. 给useState加
2. 确保initialValues有明确结构让TS能推导
不然TS根本不知道T长什么样,自然后面用data.email也不会报错——因为它认为data是any或者never。