打字稿。将通用<T>键定义为字符串

时间:2020-07-19 07:28:44

标签: typescript react-hooks typescript-typings

我想获取一些对象并通过键将其修改为字符串并返回对象。

const useInputs = <T extends Record<string, string>>(defaultValue: T) => {
    const [inputsData, setInputsData] = useState<T>(defaultValue)

    type UpdateFunction = (key: string) => (el: React.FocusEvent<HTMLInputElement>) => void
    const updateData: UpdateFunction = key => el => {
        const newInputsData = {...inputsData}
        newInputsData[key] = el.target.value
        setInputsData(newInputsData)
    }

    return { inputsData, updateData }
}

但是我在newInputsData[key] = el.target.value上出错 “类型'string'不能用于索引类型'T'.ts(2536)”

我该如何解决? 谢谢

1 个答案:

答案 0 :(得分:2)

由于通用类型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可以用其 values 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的{​​{1}}始终是value