7 tips to improve your Typescript
Published: 2022-05-28 18:53:16

To master your TS skills you need to delve into the topic of types and explore their full possibilities. This article is dedicated only to types in particular.

So to improve your skills and to study how the functionality of types can improve the development, I strongly recommend at least getting acquainted with:

- Generics
- Template literal
- Indexed Access Types
- Utility types
- KeyOf/TypeOf operators
- Conditional types
- Mapped types

While learning current examples will be used for each case:

```ts

type Todo = {
text: string,
type: "very urgent" |"urgent" | "not urgent",
daysToFinish: number,
isFinished: boolean
}

```

* Pay attention that provided information is not full as not intended to be and it only describes the most popular use cases. You can find full info that covers 100% of the topic you can find on the [official TS website.](https://www.typescriptlang.org)

And now let’s start with…

## Generics

The main aim of generics is to provide re-usability.

Let’s learn with our todo example.

```ts
type Todo = {
text: string,
type: "very urgent" |"urgent" | "not urgent",
daysToFinish: number,
isFinished: boolean
}
```

Here no generics are used and each property has its own defined type.
And here generic is implemented:

```ts
type FirstTodo<T> = {
   text: T,
   type: "very urgent" |"urgent" | "not urgent",
   daysToFinish: number,
   isFinished: boolean
}
```
What does T mean?
T that provided is the triangle brackets defines type and text has the same T type.

For example, we want to give a type string for text property (that’s logical) and we have no problems with TS.

```ts
const firstTodo: FirstTodo<string> = {
   text: "first todo",
   type: "urgent",
   daysToFinish: 6,
   isFinished: false
}
```

And now let’s add generics for each of the property:

```ts
type SecondTodo<S, T, N, B> = {
   text: S,
   type: T,
   daysToFinish: N,
   isFinished: B
}
 
type TypeOptions = "very urgent" |"urgent" | "not urgent"
 
const secondTodo: SecondTodo<string, TypeOptions, number, boolean> = {
   text: "first todo",
   type: "urgent",
   daysToFinish: 6,
   isFinished: false
}
```
Here we also set logical types for each property, but for example, we set for B not boolean but a number that will mean that “isFinished” should equal to the number. As well for  “T” we set his own custom type that could be changed with another type.


## Template literal

It works rather similar to JS and here we create a special type to collect all levels of urgency.

```ts
type UrgencyRate = "very " | "" | "not "
 
type ThirdTodo<T> = {
   text: T,
   type: `${UrgencyRate}urgent`,
   daysToFinish: number,
   isFinished: boolean
}
 
const thirdTodo: ThirdTodo<string> = {
   text: "third todo",
   type: "not urgent",
   daysToFinish: 6,
   isFinished: false
}
```

## Indexed Access Types

With Indexed Access we can create a type that can access a type of property of another type by putting one (or some) names of the properties in the square brackets.

```ts
type Todo = {
text: string,
type: "very urgent" |"urgent" | "not urgent",
daysToFinish: number,
isFinished: boolean
}
 
type TodoText = Todo["text"] // string
 
type TodoTextAndIsFinished = Todo["text" | "isFinished"] // string | boolean
 
```

With **keyOf operator** get all the properties types for 1 element.

```ts
type AllTypes = Todo[keyof Todo] // string | number | boolean
```

And with **typeOf operator** we can get the type.

```ts
typeof "todo" // console.log -> string
```

It is the way how to create a new type according to the current element.

```ts

const forthTodo: Todo = {
  text: "forth todo",
  type: "not urgent",
  daysToFinish: 6,
  isFinished: false
}
 
type AllTodoTypes = typeof forthTodo
 
// type AllTodoTypes = {
//   text: string;
//  type: "very urgent" | "urgent" | "not urgent";
//   daysToFinish: number;
//   isFinished: boolean;
}

```

## Utility types

We will observe only 6 Utility types and they are Required, Readonly, Partial, Record, Pick, Omit and NonNullable.

**Readonly** doesn’t allow to change the object from outside.

```ts
type Todo = {
text: string,
type: "very urgent" |"urgent" | "not urgent",
daysToFinish: number,
isFinished: boolean
}
 
const fifthTodo: Readonly<Todo> = {
  text: "fifth todo",
  type: "not urgent",
  daysToFinish: 6,
  isFinished: false
}
 
fifthTodo.text = "new text" // Cannot assign to 'text' because it is a read-only property.

```

Required and Partial (Required<Type> vs Partial<Type>)

Required and Partial define if all properties of the type are mandatory. 

```ts
type Todo = {
text: string,
type: "very urgent" |"urgent" | "not urgent",
daysToFinish: number,
isFinished: boolean
}
 
const todoWithRequired: Required<Todo> = {
   text: "todo",
   type: "very urgent",
   daysToFinish: 10,
   isFinished: true
}
 
const todoWithPartial: Partial<Todo> = {
   text: "todo",
   type: "very urgent"
}
```

**Record (Record<Keys, Type>)** is a constructor that helps to create a new type as an object of properties and types.

```ts
type TodosText = "first todo" | "second todo"
 
type TodoDescription = {
   type: "very urgent" |"urgent" | "not urgent",
   daysToFinish: number,
   isFinished: boolean
}
 
const todos: Record<TodosText, TodoDescription> = {
   "first todo": {
       type: "not urgent",
       daysToFinish: 10,
       isFinished: false
   },
   "second todo": {
       type: "urgent",
       daysToFinish: 0,
       isFinished: false
   }
}
```

**Pick and Omit (Pick<Type, Keys> vs Omit<Type, Keys>)**
They help to create a new type by picking/or omitting the properties.

```ts
type Todo = {
text: string,
type: "very urgent" |"urgent" | "not urgent",
daysToFinish: number,
isFinished: boolean
}
 
type todoWithPick = Pick<Todo, "text" | "isFinished">
 
const todoPick: todoWithPick  = {
   text: "todo",
   isFinished: true
}
 
type todoWithOmit = Omit<Todo, "isFinished">
 
const todoOmit: todoWithOmit = {
   text: "todo",
   type: "not urgent",
   daysToFinish: 40
}
```

**NonNullable** creates new type by excluding undefined and null from the initial type:

```ts
type PossibleNullishTodo = {
text: string,
type: "very urgent" |"urgent" | "not urgent",
daysToFinish: number,
isFinished: boolean
} | null | undefined
 
 
type NotNullishTodo = NonNullable<PossibleNullishTodo>
// type NotNullishTodo = {
//   text: string;
//   type: "very urgent" | "urgent" | "not urgent";
//   daysToFinish: number;
//   isFinished: boolean;
}

```

## Conditional types

Conditional types are aimed to define the relations between types.

```ts
type Todo = {
text: string,
type: "very urgent" |"urgent" | "not urgent",
daysToFinish: number,
isFinished: boolean
}
 
interface UrgentTodo extends Todo {
   type: "very urgent"
}
 
type ConditionalType = UrgentTodo extends Todo? true : false // true

```

## Mapped types

A mapped type is created on the base of the other types and helps to avoid repeating code. Let’s add readonly and to each property and make it optional while mapping.
 
```ts
type Todo = {
text: string,
type: "very urgent" |"urgent" | "not urgent",
daysToFinish: number,
isFinished: boolean
}
 
 
type customType<Type> = {
 readonly [Property in keyof Type]?: Type[Property];
};
 
type ReadonlyPerson = customType<Todo>
 
//type ReadonlyPerson = {
   //readonly text?: string | undefined;
   //readonly type?: "very urgent" | "urgent" | "not urgent" | undefined;
   //readonly daysToFinish?: number | undefined;
   //readonly isFinished?: boolean | undefined;
}
```

That is all for types intro, I hope you enjoined it :)