出现该错误是因为泛型类型 T 可以使用扩展 Record<string, string> 的任何类型实例化。
考虑类型
interface Inputs {x: string; y: string}
虽然Inputs 扩展了Record<string, string>,但允许使用任意字符串对其进行索引是完全不安全的。
换句话说,我们不想要下面的
const i: Inputs = {x: "A", y: "B"};
const [inputs, setInputs] = useInputs(i);
setInputs("name")(event);
因为Inputs 没有name 属性。
因此,您的内部类型UpdateFunction 必须接受一个参数,该参数是任何类型T 的有效键,用于任何实例化。这样的类型写成keyof T,如(key: keyof T) => (el: React.FocusEvent<HTMLInputElement>) => void。
放在一起应该是这样的
const useInputs = <T extends Record<string, string>>(defaultValue: T) => {
const [inputsData, setInputsData] = useState<T>(defaultValue);
type UpdateFunction = (key: keyof T) => (el: {target: {value: T[keyof T]}}) => void;
const updateData: UpdateFunction = key => el => {
const newInputsData = {...inputsData};
newInputsData[key] = el.target.value;
setInputsData(newInputsData)
};
return [inputsData, updateData] as const;
};
注意外部函数返回值的调整,允许常规使用你的钩子。
const [inputs, setInputs] = useInputs(...);
相对于非正统
const {inputsData, updateData} = useInputs(...);
此外,请注意,为了进行上述类型检查,我们还必须调整第二个参数el 的类型,它提供了将属性从React.FocusEvent<HTMLInputElement> 更新为{target: {value: T[keyof T]}} 的值出于上面讨论的相同原因,通用T 可以使用其值 是string 子类型的属性进行实例化,例如"despair" 和"elation"。
替代方法包括为el 使用更精确的类型,例如
Omit<React.FocusEvent<HTMLInputElement>, "target"> & {
target: { value: T[keyof T] };
}
移动改变方式useInput 是通用的,从整个输入对象的类型到该对象的属性键。
const useInputs = <K extends string>(defaultValue: Record<K, string>) => {
const [inputsData, setInputsData] = useState(defaultValue);
type UpdateFunction = (
key: K
) => (el: React.FocusEvent<HTMLInputElement>) => void;
const updateData: UpdateFunction = key => el => {
const newInputsData = { ...inputsData };
newInputsData[key] = el.target.value;
setInputsData(newInputsData);
};
return [inputsData, updateData] as const;
};
我更喜欢最后一个,因为HTMLInputElement 的value 始终是string。