Typescript for frontend developers
Tags: typescript, webdev, frontend
Published: 2023-01-14 05:56:29

Hey developers! I know you are working on an exciting project. I assume that, while starting the project, a person from management asked you to use Typescript and you are aware of TS. A year ago when Typescript was an immense fear for me, I never thought I will be using it in my every project and loving it. Typescript is an excellent language that has many cool features like 

- Catches errors at compile-time.
- Provides documentation.
- Allows editors to flag errors while typing and provides completion.
- Makes refactoring less error-prone.
- Makes some tests unnecessary.
- Disallows many JavaScript-type coercions.
- Very helpful in frontend frameworks like Vue and React, obviously in angular too

Typescript comes with a serious learning curve, and no doubt for beginners, it can give you a tough time but you have to learn it and grasp its advanced concepts because now the industry demands this technology almost in all new projects. But stay with me, because I am going to tell you some senior typescript concepts, I know you‘ll be loving them. Let’s go developers.

## Remember major Types in typescript

- `"Hello"` is a string
- `2` is a number
- `true/false` is a boolean
- `{}` is an object
- `[]` is an array
- `NaN` is not a number, not basically a real type.
- `null` is empty or unknown
- `undefined` means no value is set

*credit: Conrad Davis Jr. on medium*

## What is type noImplicitAny

Typescript’s default behavior is to set the default type to function, or variable and mostly it types `any` for variables. Here is the function;

```tsx
function testijgNoImplicitAny(args) {
  return args;
}
```

![https://res.cloudinary.com/dzonsuv6y/image/upload/v1673409571/post5/Screenshot_2023-01-11_085911_cpjz6n.png](https://res.cloudinary.com/dzonsuv6y/image/upload/v1673409571/post5/Screenshot_2023-01-11_085911_cpjz6n.png)

So here typescript will infer type automatically. Typescript gives you control over types. If making a variable typed is mandatory then there is an option to tell the compiler to produce an error on untyped code, so here is how to do this

Add `"noImplicitAny": true`

```json
{
  "compilerOptions": {
    "target": "esnext",
    "noImplicitAny": true    -> here is the line
}
```

underlined error in red color - very nice it is working

![https://res.cloudinary.com/dzonsuv6y/image/upload/v1673410510/post5/Screenshot_2023-01-11_091438_qgsiax.png](https://res.cloudinary.com/dzonsuv6y/image/upload/v1673410510/post5/Screenshot_2023-01-11_091438_qgsiax.png)

To remove this error  we can use any type such as `any`, `number, string`

## What is type unknown

The `unknown` type is similar to `any` type in that all types are assignable to `any`, and `unknown` type.

- Assignments to and from **`unknown`** require a type check, whereas assignments to and from **`any`** do not.
- The **`unknown`** is a more restrictive type than **`any`**, meaning that fewer operations are allowed on values of type **`unknown`** without a type check.
- **`unknown`** can be narrowed to a more specific type using type guards, whereas **`any`** cannot.

```tsx
function example1(arg: any) {
  const a: str = arg; // no error
  const b: num = arg; // no error
}

function example2(arg: unknown) {
  const a: str = arg; // 🔴 Type 'unknown' is not assignable to type 'string'.(2322)
  const b: num = arg; // 🔴 Type 'unknown' is not assignable to type 'number'.(2322)
}
```

`any` type is bidirectional, whereas `unknown` is unidirectional.

## **Union and intersection types**

An **intersection type** is a type that combines several types into one; a value that can be any one of several types is a union type. The **& symbol** is used to create an intersection, whereas the **| symbol** is used to represent a union.

### **Union types**

Are joining multiple types with the "pipe" (**`|`**) symbol. For example, the type **`string | number`** is a union type that can be either a string or a number.

```tsx
let value: string | number;

value = "hello";
console.log(value.toUpperCase()); // okay, value is of type 'string'

value = 42;
console.log(value.toFixed(2)); // Error: value.toFixed is not a function
```

### **Intersection types**

Are joining multiple types with the "ampersand" (**`&`**) symbol.  For example, the type **`string & number`** is an intersection type meaning one variable is a string and the other is a number.

```tsx
interface A {
    x: number;
}

interface B {
    y: string;
}

let value: A & B;

value = { x: 1, y: "hi, ts developers" }; // okay
console.log(value.x); // 1
console.log(value.y); // "hi, ts developers"
```

Using **Union and Intersection** types together rock, for example

```tsx
interface Person {
  name: string;
  age: number;
}
interface Employee {
  employeeId: number;
  salary: number;
}
interface Student {
  studentId: number;
  major: string;
}

type PersonType = Person & (Employee | Student);

let person: PersonType;
person = {
  name: "John",
  age: 25,
  employeeId: 123,
  salary: 50000
};

console.log(typeof person.name, person.name); // string John
console.log(typeof person.employeeId, person.employeeId) ; // number 123
```

## How to use **`Keyof` in typescript**

In TypeScript, the **`keyof`** operator is used to create a new type that represents the keys of a given object type. The new type is a union of the string literals of the object's property names. To be honest, since I knew this type I am learning to use it. I mostly use it with Typescript record types. 

A bit of usage of `keyof`

```tsx
interface Person {
    name: string;
    age: number;
}

type PersonKeys = keyof Person;

let key: PersonKeys;
key = "name"; // okay
key = "age"; // okay
key = "address"; // Error: "address" is not a property of type 'PersonKeys'
```

**`PersonKeys`** is a union of the string literals of the property names of **`Person`**, only the values **`"name"`** and **`"age"`** are valid values for **`key`.**

### Use-case of using keyof

```tsx
interface Car {
    make: string;
    model: string;
    year: number;
}

const myCar: Car = {
    make: "Toyota",
    model: "Camry",
    year: 2020
};

function updateCar(
    car: Car,
    prop: keyof Car,
    value: string | number
) {
    car[prop] = value;
}

// 👍 okay
updateCar(myCar, "make", "Honda");
// 👍 okay
updateCar(myCar, "year", 2022);

// 👎 Error: "color" is not a property of type 'keyof Car'
updateCar(myCar, "color", "red");
// 👎 Error: "year" is not a property of type 'string'
updateCar(myCar, "year", "2022");
```

There is an interface **`Car`** that defines a car with three properties: **`make`**, **`model`**, and **`year`**. There is an object named `myCar` interfaced `Car`. 

The **`updateCar`** function takes three arguments:

- **`car`** of type **`Car`**.
- **`prop`** of type **`keyof Car`**.
- **`value`** of type **`string | number`** which can be **`string`** or **`number`** based on the prop.

The function modifies the value of the property identified by **`prop`** to **`value`**.

## How **Typeof in Typescript is different**

JavaScript already has a `typeof` operator you can use in an *expression* context:

```jsx
// Prints "string"
console.log(typeof "Hello world");
```

> TypeScript adds a `typeof` operator you can use in a *type* context to refer to the *type* of a variable or property.
> 

```tsx
let text: string;
const firstName: typeof text;
const lasrName: typeof text;
```

`firstName, lastName` are a variable whose type is inferred using the **`typeof`** operator and the value of `text`. Because **`text`** is of type **`string`**.

## ReturnType in typescript

The **`ReturnType`** utility type in TypeScript is used to extract the return type of a function or method.

```tsx
function add(a: number, b: number): number {
    return a + b;
}

let addReturnType: ReturnType<typeof add>;
```

**`add`** is a function that takes two numbers as arguments and returns a number. 

**`addReturnType`** is a variable whose type is inferred using the **`ReturnType`** utility type and the function **`add`**.  And **`ReturnType<typeof add>`** is the same as **`number`**.

### Use-case of ReturnType

Here is a real-world example using array functions. 

data

```tsx
const data = [
	{ id: 1, name: 'John' }, // id -> number, name -> string
	{ id: 2, name: 'Jane' }
];
```

implementing some array functions

```tsx
const findById = (id: number) => data.find(item => item.id === id);
const getName = (item: any) => item.name;
const toUpperCase = (str: string) => str.toUpperCase();
```

extracting `ReturnType` and storing them in variables

```tsx
let foundItem: ReturnType<typeof findById> = findById(1);
console.log(foundItem); // { id: 1, name: 'John' } // { id: number, name: string }

let itemName: ReturnType<typeof getName> = getName(foundItem);
console.log(itemName); // 'John'  // string

let upperCasedName: ReturnType<typeof toUpperCase> = toUpperCase(itemName);
console.log(upperCasedName); // 'JOHN' // string
```

## What are Generics in Typescript

As per typescript docs, 

> Being able to create a component that can work over a variety of types rather than a single one. This allows users to consume these components and use their own types.
> 

When you create a generic component, you can specify a placeholder type (also called a type parameter) that represents the actual type that the component will work with. Mostly this parameter is set to T like <T> and these angle brackets are known as `type-interface`.

### Syntax of Generics

A simple generic function that takes an array of elements and returns the first element of the array:

```tsx
function syntax<T>(arg: T): T {
    return arg;
}
```

**`T`** is the type parameter that represents the actual type of elements in the array. When you call the `syntax` function, you can specify the type of elements by providing a type argument. So in simple words, arguments will decide the final type. 

```tsx
function getFirst<T>(arr: T[]): T {
    return arr[0];
}

let numbers = [1, 2, 3];
let firstNumber = getFirst<number>(numbers); // 1

let strings = ['a', 'b', 'c'];
let firstString = getFirst<string>(strings); // 'a'
```

type of the elements in the array is inferred from the array argument.

## Using **Conditional types in Typescript**

The conditional ternary operator is a very well-known operator in Javascript. The ternary operator takes three operands. A condition, a return type if the condition is true, and a return type is false.

************syntax************

```tsx
condition ? returnTypeIfTrue : returnTypeIfFalse;
```

Before going forward a bit of note about extending types

<aside>
💡 In TypeScript, you can use the **`extends`** keyword to create new types that are based on existing types.

```tsx
interface Person {
    name: string;
    age: number;
}

interface Employee extends Person {
    salary: number;
}
```

</aside>

Here is how to use conditional types by leveraging generics `<T>`.

```tsx
interface IdWithString {
  id: string;
}

interface IdWithNumber {
  id: number;
}

type Id<T> = T extends string ? IdWithString : IdWithNumber;

let idA: Id<string> = { id: "stringId" };
// Type is inferred as IdWithString

let idB: Id<number> = { id: 1 };
// Type is inferred as IdWithNumber
```

The **`Id`** type is a generic type that takes a type parameter **`T`**, and it uses a conditional type to check if **`T`** is assignable to **`string`**

## Using *typeof*, and *keyof* types together

It is rocking to use **`keyof`** and **`typeof`** together in TypeScript. The **`keyof`** keyword allows you to extract the keys of an object type, while **`typeof`** allows you to extract the type of a variable.

so here is how

```tsx
const colors = {
  red: '#ff0000',
  green: '#00ff00',
  blue: '#0000ff',
}; // 1

type Colors = keyof typeof colors; // 2, note

let selectedColor: Colors = 'green'; // 3

let anotherSelection: Colors = 'yellow'; // 4

console.log(colors[selectedColor]); // 5

// docs 
/**
1. Type '"yellow"' is not assignable to type '"red" | "green" | "blue"'
2. trying to get types of properties
3. yeah, it is correct 
4. Type '"yellow"' is not assignable to type '"red" | "green" | "blue"'
5. '#00ff00'

note: keyof typeof colors is equal to "red" | "green" | "blue" 
*/
```

## ****How the TypeScript Record Type Works****

The **`Record`** type is used to create an object type that has a set of properties with a specific key type and a specific value type. For a beginner, it is a bit tough to understand types per data type. 

When I was young in typescript I usually use an empty object `{}` as a type of object, then IDE told me, dude! there is a Record type, use that. 

************Syntax of Record type************

```tsx
Record<K extends keyof any, T>
```

Where **`K`** is the type of the keys of the object and **`T`** is the type of the values of the object.

```tsx
type TRecords = Record<string, number>;

let tryingRecords: TRecords = {
    "key1": 1,
    "key2": 2,
};
```

`tryingRecords` is an object type that has properties with keys of type **`string`** and values of type **`number`**.

### Using typescript Records and union types together

A practical example of how you can use **`Record`** and union types together in TypeScript:

```tsx
interface User {
    id: number;
    name: string;
}

interface Admin {
    id: number;
    name: string;
    permissions: string[];
}

interface Guest {
    id: number;
    name: string;
    expiration: string;
}

type UserType = "user" | "admin" | "guest";

type Users = Record<UserType, User | Admin | Guest>;

let users: Users = {
    user: { id: 1, name: "John" },
    admin: { id: 2, name: "Jane", permissions: ["read", "write"] },
    guest: { id: 3, name: "Jim", expiration: "2022-12-31" }
};
```

1. There are three **interfaces** that represent different types of users (`User`, `Admin`, and `Guest`),
2. There is a union type **`UserType`** to combine three types into a single type that can be one of **`"user"`**, **`"admin"`** or **`"guest"`.** 
3. There is an object with **`Record`** type to define a new type **`Users`.**
Type `Users` is an object type with **properties** that have keys that can only be the string literals **`"user"`**, **`"admin"`**, and **`"guest"`**, 
4. **values** of type **`User | Admin | Guest`** which is a union of all the three interfaces.

### Using record types with `typeof`, and `keyof` without interfaces

But there is a better way to type objects without even the use of interfaces, and I name it time-saving types.  

********data********

```tsx
// Data
const productInventory = {
    shirts: {
        small: 10,
        medium: 15,
        large: 20
    },
    pants: {
        small: 5,
        medium: 10,
        large: 15
    },
    shoes: {
        size7: 25,
        size8: 30,
        size9: 35
    }
};
```

**********logic**********

```tsx

type Product = keyof typeof productInventory;
type ProductSizes = typeof productInventory[Product];

function checkInventory(product: Product, size: keyof ProductSizes): boolean {
    return productInventory[product][size] > 0;
}

console.log(checkInventory("shirts", "medium")); // true
console.log(checkInventory("pants", "small")); // true
console.log(checkInventory("shoes", "size9")); // true
console.log(checkInventory("pants", "extra large")); //compile error, extra large is not key of ProductSizes
```

1. **`productInventory`** → information about different products and the sizes available for each product,  **`Record`** type to define a new type **`ProductSizes`**
2. **`ProductSizes`** is an object type with properties that have keys of the 
3. **`productInventory[Product]`** object and values of type number.
4. **`Product`** is the keyof **`productInventory`** to reference the specific product. 
5. **`ProductSizes`** is the type of **`productInventory[Product]`** to reference the specific product sizes.

## Closing words

For front-end developers, TypeScript can provide several benefits to improve the development experience. TypeScript can help catch errors early by providing better type safety. which can make your code more robust and maintainable. 

Using TypeScript in frontend development can also help you take advantage of modern JavaScript features such as JSX, async/await, and destructuring, while still providing the benefits of static type checking. This can make it easier to write and maintain complex and large-scale applications.