简体   繁体   中英

How to narrow down union of generic types in Typescript?

I'm trying to create a form component in React that uses generic types.

The generic type for the form is the initialValues that are passed in.

I can't for the life of me seem to get Typescript to narrow down the type that corresponds to a particular field.

Here's a CodeSandbox with one of my many attempts at trying to get this to work correctly.

I've tried many other things, like adding a 'type' property to each of the fields and then using a switch statement to try and narrow it down that way, but none of them have seemed to work!

Not looking anyone to write this code for me, but a nudge in the right direction would be greatly appreciated.

So as you've probably guessed by now, to do this you can't just define an indexable type like

type FormValues<T> = {
  [key: string]: T
}

Because T in this case will be a union that doesn't help too much.

So you can't use indexable type, now the only way I can think stop trying to create the type yourself and just let typescript infer it for you.

Firstly let me change your FormValue declaration a bit, I'll remove ValueOf , while I was playing with your sandbox I forgot what was its purpose, but if I don't think it matters in the way I'm suggesting. So let's type it like this:

type FormValue<TName extends string, TValue> = {
  value: TValue
  name: TName
  pristine: boolean
  errors: string[] | null
}

Now this will just take two type arguments, one is the name, another is the value, no unnecessary objects. Now let's type InitialFormValues : it will be just an object with random properties, we won't use it to type anything, only to constraint types:

type InitialValues = {
  [key: string]: any
}
// or
type InitialValues = Record<string, any>

Next comes UseFormProps . Here we avoid creating indexable type, that doesn't lead to anything, but we let typescript infer the type for us. We only need to constraint it a bit so that it's not possible to pass pe a number as initialValues :

type UseFormProps<T extends InitialValues> = {
  initialValues: T
}

Now typescript will automatically choose T and it will be exactly what we need: if initialValues is { foo: 1, bar: 'str' } , it will be { foo: number, bar: string } and nothing more.

Last goes FormValues :

type FormValues<T extends InitialValues> = {
  [x in keyof T]: FormValue<x, T[x]>
}

Now FormValues will not be just indexable, but it will contain exactly the properties T contains, also T[x] will not be a union like T[keyof T] , it will be different for each value of x so for each property. That's basically it, now it should work as expected. You'll only need to change something in transformInitialValues , because typescript will now complain if you assign empty object to FormValues<whatever>

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