【问题标题】:Typescript. Define generic <T> keys as string打字稿。将通用 <T> 键定义为字符串
【发布时间】:2020-07-19 07:28:44
【问题描述】:

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

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)"

我该如何解决这个问题? 谢谢

【问题讨论】:

    标签: typescript react-hooks typescript-typings


    【解决方案1】:

    出现该错误是因为泛型类型 T 可以使用扩展 Record&lt;string, string&gt; 的任何类型实例化。

    考虑类型

    interface Inputs {x: string; y: string} 
    

    虽然Inputs 扩展了Record&lt;string, string&gt;,但允许使用任意字符串对其进行索引是完全不安全的。

    换句话说,我们不想要下面的

    const i: Inputs = {x: "A", y: "B"};
    const [inputs, setInputs] = useInputs(i);
    setInputs("name")(event);
    

    因为Inputs 没有name 属性。

    因此,您的内部类型UpdateFunction 必须接受一个参数,该参数是任何类型T 的有效键,用于任何实例化。这样的类型写成keyof T,如(key: keyof T) =&gt; (el: React.FocusEvent&lt;HTMLInputElement&gt;) =&gt; 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&lt;HTMLInputElement&gt; 更新为{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;
    };
    

    我更喜欢最后一个,因为HTMLInputElementvalue 始终是string

    【讨论】:

    • 感谢您的回答。关于常规使用 [] 而不是 {} 作为返回值,您是绝对正确的。但(key: keyof T) 无济于事。我在同一个地方有一个错误:Type 'string' is not assignable to type 'T[keyof T]' 我不知道为什么会发生,因为我使用 Record.
    • @PetrTcoi,我看到了这个问题。我已经更新了一个类型检查和一些替代方法的版本
    • 哦...{target: {value: T[keyof T]}} 工作!你救了我。谢谢
    • @PetrTcoi 是的。有几种不同的方法。我的偏好实际上是最后一个,是我在答案末尾添加的。
    • @PetrTcoi 如果它对您有用,您愿意接受吗?
    猜你喜欢
    • 2020-02-09
    • 2022-01-03
    • 1970-01-01
    • 1970-01-01
    • 2021-08-20
    • 2021-09-22
    • 1970-01-01
    • 2018-01-18
    • 1970-01-01
    相关资源
    最近更新 更多