DEV Community

Pavel Litkin
Pavel Litkin

Posted on

Simple TypeScript tricks for scalable apps

TypeScript gains popularity each day. We use TypeScript because it makes development safer and faster. Here is a set of tricks and guidelines for a more rigorous use of TypeScript. You need to get used to them once - and they will save a lot of time in the future.

Do not use any

The trick is, instead of ignoring the problem - "legalize" it:
type TODO_ANY = any

Pros:

• You do not have to be desperate and write props.
• You are deliberately showing that the code should be improved in the future.
• This type is easy to find by searching for the project.

Restrictions:

• Using this technique too often is unlikely to be beneficial.
• Conscientiousness and responsibility are needed to remove such gaps in the future.

You may also find it useful:
export type TODO_UNKNOWN = unknown;

In the case of any, the compiler will allow almost any action. In the case of unknown, on the contrary, almost any use will lead to an error.

Last word, do not use any even if type does not matter, for example, instead of this:
const getLength = (arr: any []) => arr.length

you can write this:
const getLength = (arr: { length: number }) => arr.length
It is not ideal, but is much better than any.

Of course best solution in this specific case is to use generics:
const getLength = <T>(arr: T[]) => arr.length;

Use readonly

It is bad practice to mutate the structures you work with. For example, in the Angular world, this immediately leads to a number of consequences in the application: problems appear with checking changes in components, after which the display is not updated after mutating the data.
But can we easily prevent all attempts to mutate data? For myself, I just formed a habit of writing readonly everywhere.

What is to be done?

There are likely many places in your application where you can replace unsafe types with readonly alternatives.

Use readonly on interfaces:

// Before
export interface Person {
  name: string;
}

// After
export interface Person {
  readonly name: string;
}

Enter fullscreen mode Exit fullscreen mode

Give preference to readonly in the types:

// Before
export type UnsafeType = { prop: number }

// After
export type SafeType = Readonly<{ prop: number }>
Enter fullscreen mode Exit fullscreen mode

Use readonly fields class wherever it is possible:

// Before
class UnsafeComponent {
  loader$ = new Loader<boolean>(true);
}

// After
class SafeComponent {
  readonly loader$ = new Loader<boolean>(true);
}
Enter fullscreen mode Exit fullscreen mode

You can use readonly on all data structures, like:
const numsFromServer = readonly number[] = [1, 2, 42]

as const

TypeScript v3.4 introduces const-assertions. It is a stricter tool than readonly types because it will pack your constant with the most precise type possible. Now you can be sure: no one and nothing can change this.

Also, when using as const, your IDE will always show you the exact type of the entity being used.

Use strict mode

TypeScript has a cool strict mode that is unfortunately disabled by default. It includes a set of rules for safe and comfortable working with TypeScript.
With strict mode, you forget about errors like undefined is not a function and cannot read property X of null. Your types will be accurate and correct.

What is to be done?

If you start a new project, just turn on strict right away and be happy.

If you already have a project without strict mode and now you want to enable it, then you will face a number of difficulties. It is very difficult to write strict code when the compiler does not signal problems in any way. So you will probably have a lot of buggy spots and the migration process makes you bored very quickly.

Generally, the best approach is to break this huge task into chunks. The strict mode itself is a set of six rules. You can enable one of them and fix any errors. Your project is now a little more rigorous. Next time, turn on another rule, correct any mistakes and keep working. In one day, you will collect the entire strict mode!

But in large projects everything is not so smooth, then you can act more iteratively. Enable the flag, and above all conflicts, add @ts-ignore and a TODO comment. The next time you work with the file, correct the type as well.

Setting --strict to true, sets all of the following options to true:

  • --noImplicitAny: If TypeScript can’t infer a type, we must specify it. This mainly applies to parameters of functions and methods: With this settings, we must annotate them.
  • --noImplicitThis: Complain if the type of this isn’t clear.
  • --alwaysStrict: Use JavaScript’s strict mode whenever possible.
  • --strictNullChecks: null is not part of any type (other than its own type, null) and must be explicitly mentioned if it is a acceptable value.
  • --strictFunctionTypes: enables stronger checks for function types.
  • --strictPropertyInitialization: Properties in class definitions must be initialized, unless they can have the value undefined.

Use Utility Types

TypeScript has a set of types that are shortcuts for conversions of other types.

I advise you to study in detail all the official documentation on Utility Types and start actively introducing them into your applications. They also save a lot of time.

From my point of view, most useful utility types are Pick and Omit, they allow you to avoid creating not essential intermediate types.

Pick<Type, Keys>

interface Todo {
  title: string;
  description: string;
  completed: boolean;
}

type TodoPreview = Pick<Todo, "title" | "completed">;

const todo: TodoPreview = {
  title: "Clean room",
  completed: false,
};
Enter fullscreen mode Exit fullscreen mode

Omit<Type, Keys>

interface Todo {
  title: string;
  description: string;
  completed: boolean;
  createdAt: number;
}

type TodoInfo = Omit<Todo, "completed" | "createdAt">;

const todoInfo: TodoInfo = {
  title: "Pick up kids",
  description: "Kindergarten closes at 5pm",
};
Enter fullscreen mode Exit fullscreen mode

Conclusion

These are just a few of the principles we use, but in our team, they are perhaps the most basic. Of course, this is not the only correct way to write code in TypeScript, but once we get used to it, we just write the code this way without thinking. From time to time, this saves us from unexpected tricky mistakes in large projects.

Top comments (0)