In this article, we're going to look over how Generics work in Typescript. Literally, it looks scary at the beginning but when we understand and breaks the overall logic of generics in chunks then they will become our best friends.
This article needs some basic understanding of typescript to advance further.
Array in typescript
Most basic type of generics we always use is defining array of particular type. For instance:number[],string[],boolean[];
type numArr = Array<number>;
type strArr = Array<string>;
type boolArr = Array<boolean>;
let numberArray: numArr = [1, 2, 3, 4];
let stringArray: strArr = ["H", "e", "l", "l", "o"];
let boolArray: boolArr = [true, false, true];
console.log(numberArray);
console.log(stringArray);
console.log(boolArray);
If we not pass any particular type then it will show error on screen like below pic.
Generic Methods
Suppose we need a method which can return middleElement of any array type passed to it. So most basic approach to find middleElement will be like this :
const middleElement = (arr: Array<any>) => {
return arr[Math.floor(arr.length / 2)];
};
let numberArray: numArr = [1, 2, 3, 4, 5];
let stringArray: strArr = ["I", "T", "A", "L", "Y"];
let boolArray: boolArr = [true, false, true];
let middle1 = middleElement(numberArray);
let middle2 = middleElement(stringArray);
let middle3 = middleElement(boolArray);
console.log(middle1); //3
console.log(middle2); //A
console.log(middle3); //false
But in this way, we will loose the TypeDefinition of the return type of method as you can see in below pic which shows 'any' when we hover over it.
Now if we implement generics feature of typescript then we can retain the typeDefinition of the method.
const middleElement = <T>(arr: Array<T>) => {
return arr[Math.floor(arr.length / 2)];
};
We can see in above pic, when we hover over any middleElement method of certain type, we see its argument passed type & its return type.
//We can explicitly define type of our method as below :
let middle2 = middleElement<string>(stringArray);
To make a method which can accept more than one generic type we can do the following :
//This method will accept any type of arguments and make a combined object of it.
const makeObj = <X, Y>(x: X, y: Y) => {
return { x, y };
};
let numbArray: numArr = [1, 2, 3, 4];
let Obj = { firstName: "Vinod", lastName: "Chauhan" };
let newObj = makeObj(numbArray, Obj);
console.log(newObj);
We can see the return type of makeObj method in above pic, this shows the power of generics with VS Code editor.
Generic Method with required fields
Let say we need a method 'makeICardDetail' which takes an object as a parameter. This object required firstname,LastName,age as a mandatory fields to fullfill its requirement. So one way of doing this will be :
const makeICardDetail = (obj: {
firstName: string;
lastName: string;
age: number;
}) => {
return {
...obj, //Copy other contents of Object as it is.
ICard: obj.firstName + " " + obj.lastName + ", " + obj.age
};
};
let makeNewDetail = makeICardDetail({
firstName: "Vinod",
lastName: "Chauhan",
age: 27
});
console.log(makeNewDetail.ICard); //Vinod Chauhan, 27
Now, What if I need to pass location also but not as a mandatory field. If I pass location field in makeICardDetail as a argument, typescript compiler gives me error on it.
let makeNewDetail = makeICardDetail({
firstName: "Vinod",
lastName: "Chauhan",
age: 27,
location: "India"
});
console.log(makeNewDetail.ICard); //Vinod Chauhan, 27
//TSC compiler
index.ts:59:3 - error TS2345: Argument of type '{ firstName: string; lastName: string; age: number; location: string; }' is not assignable to parameter
of type '{ firstName: string; lastName: string; age: number; }'.
Object literal may only specify known properties, and 'location' does not
exist in type '{ firstName: string; lastName: string; age: number; }'.
59 location: "India"
~~~~~~~~~~~~~~~~~
[9:01:00 PM] Found 1 error. Watching for file changes.
Here Generics come to rescue us with its 'extends' feature.
const makeICardDetail = <T extends { firstName: string; lastName: string; age: number }>( obj: T) => {
return {
...obj,
ICard: obj.firstName + " " + obj.lastName + ", " + obj.age
};
};
And if you look at below pic, you can see 'makeNewDetail' variable gives option of all possible values in it which help us in complex and lengthy application.
Interface with Generics
interface KeyPair<T, U> {
key: T;
value: U;
}
let kv1: KeyPair<number, string> = { key: 1, value: "Vinod" }; // OK
let kv2: KeyPair<number, number> = { key: 2, value: 12345 }; // OK
Generics in React
If you have ever worked on React with typescript, you must have implemented functional components where 'props' has to be passed. props provide typedefinition to components.
import React from "react";
interface Props {
name: string;
}
export const Example: React.FC<Props> = ({ name }) => {
return <div>Hello {name}</div>;
};
In above snippet, "React.FC" props are passed as generic to it which is type of interface where fields have there type declared with them.
Also we can define generics with useState.
export const Example: React.FC<Props> = ({name}) => {
//const [state] = React.useState({fullName:"",age:0});
const [state] = React.useState<{fullName:string | null;age : number}>({fullName : "",age:0});
return <div>Hello {name}</div>;
};
I hope this article will add a small amount of knowledge in your react learning with typescript.
Top comments (4)
Great article and a complex topic broken down well.
I found this the other day which has also been quite helpful. github.com/typescript-cheatsheets/...
This article is good, covered a good part of TypeScript.
It would be great to know more about React with TypeScript.
Thanks!
Sure Nikhil, Will get back soon with it.
Really nice article. In the case of adding an optional field, you could also use an index signature to specify optional fields.