I have a type defined like so:
type FieldToAction = { [key in ApiActions]: ((field: Field) => void) | ((field: Field, fieldValue: FieldValue) => void) }
where FieldValue
is a union of types:
type FieldValue = string | string[] | AssigneeValue | PriorityValue
I have a variable object where I then declare the functions set by the above types:
const fieldToAction: FieldToAction = { . . . "SET_DESCRIPTION": (field: Field, fieldValue: string) => field.setDescription(fieldValue), "SET_LABELS_VALUE": (field: Field, fieldValue: string[]) => field.setValue(fieldValue) };
This produces an error where the values in the object are not of type FieldToAction
, I kind of understand why since I am now constraining the parameter to be a string
or string[]
.
My question is: Is there a way of still using the union of types’ type and constraining the value in the parameter?
Thank you
Advertisement
Answer
Instead of using union |
you should use intersection &
. Intersection of functions produces a function overloading.
type FieldToAction = { [key in ApiActions]: ((field: Field) => void) & ((field: Field, fieldValue: FieldValue) => void) }
For instance, imagine you have this:
type A = (a: string) => void type B = (b: number) => void type Union = A | B declare const fn: Union fn() // fn expects `never`
fn
expects never
because TS is trying to find most common type, which will conform each function in a union. To find such common type, TS intersects function arguments. It means that fn
expects string & number
, which in turn evaluates to never
. See this legendary answer.
Let’s try this:
type A = (a: string) => void type B = (b: 'a') => void type Union = A | B declare const fn: Union fn() // expects "a"
Now, fn
expects "a"
because it is safe to provide this to both functions. string & "a" === "a"
.
This is why you almost never want to end up with union of functions.