简体   繁体   中英

Typescript: How to defined type of object key for generic type

I have some piece of code like below. I write custom hook with generic type for defined custom type return of my hook.

type Return<T> = [T, (e: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => void, Record<keyof T, boolean>, () => void]


function useInput<T>(): Return<T> {
    const [input, setInput] = useState<T>({} as T);
    const [isDirty, setDirty] = useState({} as Record<keyof T, boolean>);

    const handleInputChange = (e: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
        setInput({
            ...input,
            [e.target.name]: e.target.value
        });
        setDirty({
            ...isDirty,
            [e.target.name]: true
        });
    };

    const resetInput = () => {
        Object.keys(input).forEach((v) => input[v] = ''); //this line i get error
        setInput({...input});
    };


    return [input, handleInputChange, isDirty, resetInput]
}

export default useInput;

My generic type T is object. But when I loop over this, I get this error Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'unknown'. How can I defined type of key for generic type T ? Please help me.

So Object.keys function is not generic parametrized, by this reason v in the forEach is always string. Here is reasoning why - Why doesn't Object.keys return a keyof type in TypeScript? .

Thats said you cannot narrow type of v it will be just string. What you can do is type assertion:

 Object.keys(input).forEach((v) => input[v as keyof T] = '')

But you will get next error as you don't define that T has all values as strings, and you assign to it empty string. This is actually correct error. You cannot assign to every field of unknown object a string.

In order to fix the second we need to narrow the type of value of T

function useInput<T extends Record<string, string>>(): Return<T>

We are saying now T has keys and values which are string or subtypes of string . Because of the fact that they can be subtypes of string we need to do the second assertion after. Why - Subtype can be for example 'a' | 'b' 'a' | 'b' and such type cannot take empty string value.

Object.keys(input).forEach((v) => (input[v as keyof T] as string) = '')

But without stating that T extends Record<string, string> we would not able to assert property type of such to string .

As I said before such implementation has an issue, its risky as if you have object which is for example type A { a: 'x' | 'y'} type A { a: 'x' | 'y'} and you pass it to such construct, then you can get empty string in property a and this is invalid member of type A . But if you work with types which have string values only, and I assume you do, then its fully ok.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM