The following article is a free sample from EasyWebDev mailing list. Subscribe to get simple web development guides, tutorials, and explanations.
If you've been using Typescript, there is a good chance that you've come across Generics. Generics are a handy Typescript feature that allows you to declare variable types for your interfaces, types, and declarations.
interface GenericInterface<T> {
prop: T;
}
T
is a Generic that allows the consumer to specify the type for our property prop
. This is handy in situations where all the possible types are not known ahead of time or where there are a wide variety of types that we don't need to constrain on the interface level.
Using Generics
Using generics in your application code is conceptually very similar to passing arguments to to your functions. However, instead of passing run time values, we are passing Typescript type definitions.
const myObject: GenericInterface<string> = {
prop: 'string'
}
Generics make our code more flexible but they still also guarantee type safety.
const myObject: GenericInterface<string> = {
prop: 1, // Error! string is passed to the generic, but a number is specified.
}
Non-Trivial Types
Complex Types
The values passed to a Generic do not need to be simple types. Any valid Typescript expression is allowed. The important take-away is that the concrete value must adhere to the Generics' type, whatever it is.
const unionObject: GenericInterface<string | number> = {
prop: 1; // this can be either a string or a number
}
const typeofObject: GenericInterface<typeof window.document> = {
prop: window.document; // this can only be an object that matches the type from window.document
}
Multiple Generics
There is no limit to the number of generics that you can define for a given interface, type, or declaration.
interface GenericInterface<A, B, B> {
prop: A;
next: B;
another: C;
}
Default Types
When a generic is defined, the caller always has to specify a type for the generic. To get around this requirement, Generics can be assigned default types. When a default type is used and a type has not been specified, Typescript will use the default Generic type.
interface GenericInterface<A = string> {
prop: A;
}
const myObject: GenericInterface = {
prop: 'string'
}
Default types allow you to do some very complex and interesting inference. Here is a silly example:
interface GenericInterface<A extends { prop: string | number }, B = A['foo']> {
a: A;
b: B
}
const obj: GenericInterface<{ prop: string }> = {
a: {
prop: 'string',
},
b: 1 // Error! b MUST match the type from a.prop.
}
Constraining Generics
By default there are no constraints on the types that can be passed to a Generic. Sometimes, however, we want to constrain a Generic's type. One way we can do this is aliasing the constraint to a new type. This applies a localized constraint and does not globally constraint the Generic's type.
type Constrained = GenericInterface<string>;
function myfunction(arg: Constrained) {
}
Another way to constraing Generics is on the Generic's definition itself. This constraint will apply to any usage of the Generic, so all callers will need to adhere to the constraint when using extends
directly in the Generic's definition.
interface GenericInterface<T extends string> {
prop: T
}
Naming Generics
This is a somewhat controversial topic, but there is a long-standing convention to use single letter names for your Generics. I would encourage you to use whatever approach works best for your team and application.
Happy coding!
Enjoy this article? Subscribe to EasyWebDev!
Top comments (0)