DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’»

Julie Cherner
Julie Cherner

Posted on

7 tips to improve your Typescript

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:


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

Enter fullscreen mode Exit fullscreen mode
  • 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.

And now let’s start with…

Generics

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

Let’s learn with our todo example.

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

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

type FirstTodo<T> = {
   text: T,
   type: "very urgent" |"urgent" | "not urgent",
   daysToFinish: number,
   isFinished: boolean
}
Enter fullscreen mode Exit fullscreen mode

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.

const firstTodo: FirstTodo<string> = {
   text: "first todo",
   type: "urgent",
   daysToFinish: 6,
   isFinished: false
}
Enter fullscreen mode Exit fullscreen mode

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

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
}
Enter fullscreen mode Exit fullscreen mode

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.

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
}
Enter fullscreen mode Exit fullscreen mode

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.

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

Enter fullscreen mode Exit fullscreen mode

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

type AllTypes = Todo[keyof Todo] // string | number | boolean
Enter fullscreen mode Exit fullscreen mode

And with typeOf operator we can get the type.

typeof "todo" // console.log -> string
Enter fullscreen mode Exit fullscreen mode

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


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;
}

Enter fullscreen mode Exit fullscreen mode

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.

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.

Enter fullscreen mode Exit fullscreen mode

Required and Partial (Required vs Partial)

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

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"
}
Enter fullscreen mode Exit fullscreen mode

Record (Record) is a constructor that helps to create a new type as an object of properties and types.

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
   }
}
Enter fullscreen mode Exit fullscreen mode

Pick and Omit (Pick vs Omit)
They help to create a new type by picking/or omitting the properties.

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
}
Enter fullscreen mode Exit fullscreen mode

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

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;
}

Enter fullscreen mode Exit fullscreen mode

Conditional types

Conditional types are aimed to define the relations between types.

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

Enter fullscreen mode Exit fullscreen mode

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.

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;
}
Enter fullscreen mode Exit fullscreen mode

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

Top comments (4)

Collapse
sadullah profile image
Sadullah TANRIKULU

Great work! Thanx!

Collapse
ivis1 profile image
Ivan Isaac

Really helpful.

Collapse
thereis profile image
Lucas Reis

Great article! I would cover type union as well 😊

Collapse
vyspiansky profile image
Ihor Vyspiansky

I like your article. Just want to add some clarity in the Required example.

Todo and Required<Todo> will behave quite similar considering your example? For instance, if you miss some property in the todoWithRequired object you will see the same type error.

However if you set some properties as optional in Todo type (let it be isFinished?: boolean or whatever), the different between Todo and Required<Todo> will be more clear, I guess. The object with type Todo will be OK without specifying these optional properties, but not Required<Todo>.

🌚 Life is too short to browse without dark mode