I have write a function with typescript in two ways, one is using generic or another is using a union type. I want to know are these two ways totally same?
export type StopDefaultEventsType =
| React.MouseEvent<HTMLElement, MouseEvent>
| React.DragEvent<HTMLElement>;
// using generic
const stopDefaultEvents = <T extends StopDefaultEventsType>(event: T): void => {
event.preventDefault();
event.stopPropagation();
};
// using union type
const stopDefaultEvents = (event: StopDefaultEventsType): void => {
event.preventDefault();
event.stopPropagation();
}
CodePudding user response:
A generic type parameter is useful for expressing a relationship between multiple parameter types, or between the parameter and return types; in your example, there is only one parameter, and the return type is void
, so there is no relationship to express and hence no need for a generic type parameter. The clue that T
is probably unnecessary is that it only appears once in the function's type declaration, so it doesn't relate anything to anything else.*
Note that "using generics" and "using a union type" aren't alternatives here; your generic version also uses a union type (as the generic type parameter's upper bound), so it's simply a matter of whether you need to also make your function generic, and in this case you don't.
*There are some cases where a generic type parameter appears only once, in the return type, typically when returning empty collections, where a generic type parameter appears only once, in the return type, but is still useful: e.g. function makeEmptyArray<T>(): T[] { return []; }
. There are also other cases where T
might still be useful even though it is only used for one parameter and not the return type. But in general this heuristic is a good one: if you only use T
once, you probably don't need it.
CodePudding user response:
In this case, the two functions are equivalent because the generic type T
does not propagate any further in the code. So, even if T
is only one member of the union type, it makes no difference to the caller and the behaviour is the same as if we just use the whole union type.
There would be a difference if the function accepted another parameter whose type depended on T
or returned a type that depended on T
. Consider a change where the function returns the event provided:
// using generic
const stopDefaultEvents = <T extends StopDefaultEventsType>(event: T): T => {
event.preventDefault();
event.stopPropagation();
return event;
};
// using union type
const stopDefaultEvents = (event: StopDefaultEventsType): StopDefaultEventsType => {
event.preventDefault();
event.stopPropagation();
return event;
}
Now, the generic function retains the specificity about what type of event is passed in. A MouseEvent returns a MouseEvent, DragEvent returns a DragEvent. Meanwhile, the union version when given a MouseEvent returns something that could be either a MouseEvent or a DragEvent (even though the author knows which it will be.)