visit
What if we wanted to define a function that gets the user's id, and we are not sure if the id will be a string or an integer? What do we do then? We could either define multiple functions to take care of the different types or use the any
keyword like the code block below.
function getId(arg: any): any {
returns arg;
}
let id1 = getId('Amara')
The above function would work in our project and not throw off any error; however, we trade-off the type definition for flexibility by using the any
type. When an argument is passed in, we have no idea what the type of the argument is and what type the function returns.
function getId<Type>(arg: Type): Type {
return arg;
}
let userOne = getId<String>('identifier1')
We add a Type
variable, then pass Type
as the type of the argument, and finally, specify that the function will return a value of Type
.
NOTE: Type is just a placeholder and not the standard, as we can replace it with any other string, e.g. <T>, <ReturnType> etc.
Creating a userOne
function, we then explicitly state that the type of the argument is a string and that the function should also return a string.
When we hover over userOne
we can see that we get a type of string, and that is due to the magic of typescript generics.
function getLastItem<Type>(arr: Type ): Type{
return arr[arr.length -1]
}
let arr1 = getLastItem([1,2,3,4,5])
console.log(arr1)
function getLastItem<Type>(arr: Type[] ): Type{
return arr[arr.length -1]
}
let arr1 = getLastItem([1,2,3,4,5])
console.log(arr1)// returns 5
The []
specifies that the argument will be an array typescript, knowing that there usually is a length property on the array allows us to do this.
interface Person<T,U>{
(firstname: T, age: U ) : void;
};
function introducePerson<T, U>(firstname: T, age: U): void{
console.log('Hey! My name is ' + firstname + ',and I am ' + age);
}
let person1: Person<string, number> = introducePerson;
person1("Amara", 12); // Output: Hey! My name is Amara, and I am 12
In the code block above, we declared a generic interface called Person
with two properties, firstname
and age
. These properties accept generic arguments that we represent with the type variables <T>
and <U>
.
With this generic interface, we can then use any method with the same signature as the interface, such as the introducePerson
method.
Creating the person1
object, we state that the object has a type of the Person
interface, signifying that person1
will have a firstname
and age
property. We then define the argument types getting passed in (string and number). Equating it to the introducePerson
allows us to have access and eventually run the function.
class Collection{
items: Array<number> = [];
add(item: number) {
this.items.push(item);
};
remove(item: number){
const index = this.items.findIndex(i=> i === item);
this.items.splice(index, 1);
return this.items
}
}
const myCollection = new Collection();
myCollection.add(1);
myCollection.remove(1);
In the example above, we created a class called Collection
. This class contains an items
array and two functions, add
and remove
, to add and remove an item from the items
array.
We then create an instance from the Collection
class myCollection
, and finally, call the add
and remove
method.
class Collection<T>{
items: Array<T> = [];
add(item: T) {
this.items.push(item);
};
remove(item: T){
const index = this.items.findIndex(i=> i === item);
this.items.splice(index, 1);
return this.items
}
}
const myCollection = new Collection<number>();
myCollection.add(1);
myCollection.remove(1);
In the block of code above, we use generic types when creating the class Collection
, so now create different instances that work with different types.
function addAge <T extends {name: string}>(obj: T) {
let age = 40;
return {... obj, age};
}
let docOne = addAge({name: 'Amara',color: 'red'});
let docTwo = addAge({series: 'killing eve',color: 'red' }); // throws an error
console.log(docOne);
In the block of code above, we use the extends
keyword to specify that we not only need the argument to be of an object type but that the object needs to have a name
property. If we replaced the name property from the argument passed into docOne
, we would have an error.
Recall an earlier example where we tried to find the last number in the array using the .length
property but got an error. We then worked around it by explicitly stating that we only accepted array arguments.
interface LengthOfArg {
length: number;
}
function logLength<Type extends LengthOfArg>(arg: Type): Type {
console.log(arg.length);
return arg;
}
logLength({length: 52, name: 'Amara'})// 52
logLength([1,2,3])//3
logLength('Orange')// 6
logLength(2)// throws off an error
In the block of code above, we use the extends
keyword to specify that we want our argument to have the length
property on the interface LengthOfArg
. Doing this allows us to pass in an object containing a length property, an array, or a word and have our function not throw an error, this is because they all have a length property on them.