Typescript "Generics" Syntax + Angle Brackets

I'm having a little bit of trouble understanding the purpose behind angle brackets in Typescript.

Often, they're used for generics. This is clearest to me for functions. For example, if you've got a function that you want to make more reusable (or "generic") you'd apply them after the function name, like so:

function identity<T>(arg: T): T { 
  return arg; 
};

This enforces type safety while also allowing you to use this function with multiple types.

However, in other cases the < and > brackets appear after other types, like in the following event handler:

const handleChange = (e: React.ChangeEvent<HTMLInputElement>): void => {
  const val = e.currentTarget.value;
  setWord(val);
};

By looking at TypeScript's auto-completion feature, the React.ChangeEvent shows this: React.ChangeEvent<Element>

  1. What does the <Element> mean in this case? Is it also a generic?
  2. Are we passing in the HTMLInputElement in, and if so, is that an interface?
  3. Finally, why is the generic in this case (if that's what's going on) passed after the React.ChangeEvent type, whereas with the function, the T generic is passed before the function body?

Solution 1:

The React.ChangeEvent<HTMLInputElement> syntax shows what is called a Generic Type. This type receives another type as "parameter" to further specify it better.

Think of a generic type similarly to a function, but for types. The functions we are used to receives one or more values and produce another value. Generic types are similar, they receive one or more types and produce a type. The generic type alone may or may not be enough to represent something, depending on whether or not it was declared with optional parameters (just like a function may or may not have optional parameters).

Here is a much simpler example of generic type: Array<T>. This is just another way of saying T[]. For example, Array<number>. Another very common generic type is Promise<T>, which represents the type of a promise that resolves to T.

The Promise is a good example of a generic type whose parameter is required:

// --------------------------------------
// | interface Promise<T>               |
// --------------------------------------
// | Represents the completion of an    |
// | asynchronous operation             |
// --------------------------------------
// | Generic type 'Promise<T>' requires |
// | 1 type argument(s).   ts(2314)     |
// --------------------------------------
//          ^
//          |
//          |
type X = Promise;

Let's say you want to represent an object with the form { foo: something } where something can be whatever you want. You would do this with a generic type:

type Foo<SomeType> = {
  foo: SomeType;
};

// Now you can declare specific types

type FooWithString = Foo<string>;
// equivalent of:
// type FooWithString = { foo: string };

type FooWithNumber = Foo<number>;
// equivalent of:
// type FooWithString = { foo: number };

If we want to make this SomeType an optional type parameter, so that our generic type Foo can also be used by itself (without passing a type via angle brackets), we just have to decide what we want its default type to be (similarly to how we can choose default values for normal functions):

type Foo<SomeType = Date> = {
  foo: SomeType;
};

Now we can use Foo without angle brackets - it would be equivalent of doing Foo<Date>.


You can find several interesting examples of types like this in the TypeScript reference for Utility Types.


Explicit answers to your questions:

1. The <Element> shows the generic type being hovered by you (i.e. React.ChangeEvent) receives one type parameter, and the person from React team that created this generic type decided to call this parameter Element.

  • In my example above, if you hover upon Foo<number> it will show you Foo<SomeType>, because that's how I declared the type.

2a. Yes, we are passing the HTMLInputElement as a parameter to the generic type React.ChangeEvent. Think of React.ChangeEvent like a "type function" that takes a type parameter and "returns" another type. So we give HTMLInputElement to React.ChangeEvent so that the resulting type React.ChangeEvent<HTMLInputElement> specifies what we want.

2b. Being or not being an interface is not relevant. An interface is just a syntax to declare a type that has a few quirks compared to a standard type Foo = /* ... */. Don't worry about these details now.

3. This is just how TypeScript developers decided the syntax should be. It matches the syntax of Java, for example, in which we have generic types such as ArrayList<String> and generic functions such as myFunc<SomeType>(a: SomeType).

Solution 2:

ChangeEvent like your function can be passed a type, so you're passing a concrete type into it. You could also pass a concrete type into your function - identity<MyType>(myArg) although you probably don't need to as the type will be inferred from the argument you pass in since they both share T.

The difference is that ChangeEvent can't infer the type from an argument as it's not a function so you need to explicitly give it one.

HTMLInputElement could be a type or interface. The ChangeEvent will vary depending on the HTML element type (a change from an input is different from a change of a select) so you pass in what type of element it is to get the complete final interface back.