Generic types are like functions for type declarations the let you pass some arguments (other types) later. This is a (part of) simple linked list implementation:
type ListNode = {
value: any;
next: ListNode|null;
}
function add(head: ListNode, value: any): ListNode {
const newElement: ListNode = { value, next: null };
let current: ListNode = head;
while (current.next) {
current = current.next;
}
current.next = newElement;
return current;
}
This code is fine, it works, but gives no type safety:
const head: ListNode = {
value: 12,
next: null
}
add(head, "Hello!")
Generics give us a way to specify: this is a LinkedList
of numbers. Or strings. Or whatever.
This is the same type, but this time written as generic:
type ListNode<T> = {
value: T;
next: ListNode<T>|null;
}
T
is the variable type argument here. When creating the head
element we'll specify the type of values in the list:
const head: ListNode<number> = {
value: 12, next: null
}
Trying to give different type of value will result in an error:
const head: ListNode<number> = {
value: "12", next: null
}
Functions can be generic too! First, let's take a look at a simple example:
function makeTuple<T>(a: T, b: T): [T, T] {
return [a, b];
}
Great thing about generic functions is that TypeScript is actually quite smart about them. This obviously works as expected:
makeTuple<number>(1, 20);
But TypeScript is also to guess the type T
based on the first argument it gets.
makeTuple("Hello", "Devmeetings")
makeTyple("Hello", 13) // error
Let's rewrite the add
function from previous example:
function add<T>(head: ListNode<T>, value: T): ListNode<T> {
const newElement: ListNode<T> = { value, next: null };
let current: ListNode<T> = head;
while (current.next) {
current = current.next;
}
current.next = newElement;
return current;
}
add(head, 12);
add(head, "That's illegal!"); // error