Learning TypeScript Part 3
===

---
###### tags: `TypeScript`
# Generics
Generics allow us to define reusable functions and classes that work with multiple types.
```typescript=
// Define an array that returns number
let thisArray: number[] = [1, 2, 4, 5];
// We can also use array generic
let thisArray: Array<number> = [1, 2, 4, 5];
```
## Built-in generic examples
```htmlembedded=
<body>
<input
id="userName"
type="text"
placeholder="userName"
value="HelloWorld"
/>
</body>
```
```typescript=
// That's get value in input element
const inputEle = document.querySelector('#userName');
console.log(inputEle);
```
Since we did not specify type, when we hover to inputEle, it will show its type as `Element | null`;

```typescript=
// Let's access value and change the value from "HelloWorld" to "WoW"
const inputEl = document.querySelector('#userName');
console.log(inputEl);
inputEle.value = "WoW";
```

Because we did not specify type, it shows errors.

We know that `Element` is a built-in type, and you can hover to `querySelector` and press `command` + right click to find out more types, see file `lib.dom.d.ts` or you can refer to [DOM type definitions](https://github.com/microsoft/TypeScript/blob/main/lib/lib.dom.d.ts).
When you add `<HTML>` after `querySelector`, you should see a list of dropdown menu.

The thing is that when you define the type, there's an error showed up said that **inpunEl is possibly 'null'**

We already know that `inpulEl` is not null, so we can add a explanation mark after `(#username)` to tell ts that `inputEl` is not null;

```typescript
// another example - button
const btnEl = document.querySeletor<HTMLButtonElement>('.btn')!;
```
## Generic function
Generic comes in handy when we need to define multiple times, for example:
```typescript
function numPassword(item: number):number {
return item;
}
function stringPassword(item: string):string {
return item;
}
function booleanPassword(item: boolean):boolean {
return item;
}
```
From the code above, each function has already define its type, so when we give a number to the first function , it will return a number for sure, but if we have more functions like this, it would be too messy to maintain.
```typescript
// maybe we can use any, would that be easier ?
function password(item: any): any {
return item;
}
```
If we use `any` type, it seems like it can take any kind of types and return it, but there's a possibility that if we pass a `string` and it will returns a `number`, or if we pass a number, it returns a `boolean`, because we use `any`, and `any` does not build the relationship between input and output value.
```typescript
// Generic
// Pass any kind of value, and it would return its type
// example 1
interface Profile{
name: string;
age: number
}
function password<T>(item: T): T {
return item;
}
const result1 = password<number>(3);
const result2 = password<string>('Hello');
const result3 = password<boolean>(false);
const result4 = password<Profile>({name: "Andy", age: 23})
// example 2
function getRandom<T>(list:T[]): T {
const ranIndex = Math.floor(Math.random() * list.length);
return list[ranIndex];
}
getRandom<string>(['Hello', 'world', 'Goodbye']);
getRandom<number>([2, 5, 6, 9]);
getRandom<boolean>([false, false, true, true]);
```
Use `generic`, we don't need to specify each function with its type, we can use `<T>` to make it as generic, and pass any type we want.
## Inferred generic type parameters
Normally, TS is smart enough to tell what types it should return.
From code below, it's as same as `let x:number = 23;`

Let's use code examples above.
```typescript
interface Profile{
name: string;
age: number
}
function password<T>(item: T): T {
return item;
}
const result1 = password(3);
const result2 = password('Hello');
const result3 = password(false);
const result4 = password({name: "Andy", age: 23})
function getRandom<T>(list:T[]): T {
const ranIndex = Math.floor(Math.random() * list.length);
return list[ranIndex];
}
getRandom(['Hello', 'world', 'Goodbye']);
getRandom([2, 5, 6, 9]);
getRandom([false, false, true, true]);
```
Even we did not specify types, TS can figure it out itself.

But in code example of built-in generic like `querySelector`, we can't use `document.querySelector('.btn')`, because `'.bnt'` would always be a `string`, in order to let TS to know that we want to return a `HTMLButtonElement` type, we need to specify it.
---
## Generics with multiple types
Based on the code above, we know that we can use generic type in our code and don't have to specify type one by one, but what if there's a function and it takes 2 different type parameters, how can we proceed? Can you guess the type of `obj1`, `obj2` and `profileObjs`?

Next, we will use generic type to improve our code.

`T` represents the `Type`, and since we have a second parameter, using `U` is sort of like a convention, if we have a third parameter, we would use `V`, `W` so on and so forth.
Once we use specify, TS automatically figures out that the `obj1` is a string, and `obj2` is a array of string based on the parameters we have sent.
---
## Adding type constraints
Let's take the code above as an example here.
```typescript
function profileObj<T,U>(obj1:T, obj2:U) {
return {
...obj1,
...obj2,
};
}
console.log(profileObj({name: 'Andy'}, 9))
```
Guess what will be printed in the console?
> A. object {name: 'Andy', 9}
> B. object {name: 'Andy'}
The answer is **B**, when we try to destruct a boolean or a number, it would be empty object, the reason why it only showed `obj1` was because we did not add a restriction in out function `profileObj` to only return objects.

We can use `extends` in this situation. Once we added `extends` to explicitly return objects, you can see there's an error of saying that `type number is not assignable to parameter of type object`, here we can change parameter from `9` to `{num: 9}`.

Alternative, we can use `interface` to explicit return some type, see example below.
We created an `interface`, but we did not extends interface `Num` so when we access `age`, it showed error.

Once we amend it, the error is gone, and we can access `length`.

We gave a parameter `fjowef`, it seemed a `string`, but you can think of like `fjowef.length` which was `6`, and `6 * 2 = 12`, but if we gave a number for example `6`, but `6.length` won't give back any number, so it's not acceptable, therefore an error occured.
```typescript
// You can assign Leng type to thing directly,
// it would get the same result.
interface Leng {
length: number;
}
function getLeng(thing: Leng) {
return thing.length * 2;
}
```
---
## Default type parameter
We can pass a default type parameter, when we don't specify the type like the first one, the type would be the default one which is `number`, and the second one would be `boolean`.

---
## Generic classes
Use generic type makes it much easier to assign a type, we only need to declare different interfaces.
```typescript
interface Song {
title: string;
composer: string;
}
interface Video {
title: string;
director: string;
}
class PlayList<T> {
public list: T[] = [];
add(item: T) {
this.list.push(item);
}
}
const songs = new PlayList<Song>;
const videos = new PlayList<Video>;
```