Typescript Best Practises

Emad Elmogy
Tradeling Technology Blog
3 min readNov 23, 2022

TypeScript practices that I follow and recommend.

Enable “strict”

The ‘use strict’ feature was added to JavaScript in ES5. It has the literal meaning which is “the code should be in strict mode”. you can find the ‘strict’ configuration in the tsconfig file.

     {
"forceConsitentCasingInFileNames": true,
"noImplicitReturns": true,
"strict": true,
"noUnusedLocals": true
}

The “strict” flag is the most important one as it covers four other flags that you can add independently:

  • noImplicitThis: Complains if the type of this isn’t clear.
  • noImplicitAny: With this setting, you have to define every single type in your application. This mainly applies to parameters of functions and methods.
  • strictNullChecks: null is not part of any type (other than its own type, null) and must be explicitly mentioned if it is an acceptable value.
  • alwaysStrict: Use JavaScript’s strict mode whenever possible.

Use correct data type annotation (Avoid the use of any)

one of the advantages you have when coding in TS is Data type annotation,
It saves you from weird run time errors that can occur by mistakes.

It is recommended that you always define the data type whenever you declare a new variable.

Instead, use unknown when you do not know the type. It is similar to any but has one difference: it can only interact after seeing the type (through something like a type guard or inference).

const name: string = "Emad";
const value: number = 100;
const hasValue: boolean = true;

const foo: any = "foo";
foo.length; // it Works, type checking is turned off

const bar: unknown = "bar";
bar.length; // Error, bar is unknown

if (typeof bar === "string") {
bar.length; // it Works,as bar is a string
}

Function parameters

It makes sense to change the function to take parameter as an object instead of parameters with the same type.

    function fn(x: string, y: string, z: string) {}

it’s quite easy to call it with the wrong order of parameters. For instance: fn(x, z, y) , better change the function to take an object as the call will look like: fn({x, y, z}) which makes it easier to spot mistakes and review code.

    function fn(foo: {x: string, y: string, z: string}) {}

Use enum

It can help you document the intention of the code.

Don’t

const GENRE = {
DRAMA: 'drama',
COMEDY: 'comedy',
DOCUMENTARY: 'documentary',
}

projector.configureFilm(GENRE.COMEDY);

class Projector {
// declaration of Projector
configureFilm(genre) {
switch (genre) {
case GENRE.DRAMA:
// some logic to be executed
}
}
}

Do

enum GENRE {
DRAMA,
COMEDY,
DOCUMENTARY,
}

projector.configureFilm(GENRE.COMEDY);

class Projector {
// declaration of Projector
configureFilm(genre) {
switch (genre) {
case GENRE.DRAMA:
// some logic to be executed
}
}

Return Types of Callbacks

Don’t use the return type any for callbacks whose value will be ignored.

// WRONG
function fn(x: () => any) {
x();
}

Do use the return type void for callbacks whose value will be ignored

// Correct
function fn(x: () => void) {
x();
}

Using void is safer because it prevents you from accidentally using the return value of x in an unchecked way.

Don’t use “bind”

Let’s take a look into the definition of bind:

    bind (thisArg: any, ...anyArray: any[]) : any

It’ll always return “any” and for bind() ,there is no type check and it accepts any type.

    function add (x: number, y: number) {
return x + y;
}

let boundAdd = add.bind(null, 111);
boundAdd(100); // Ok but no type checked
boundAdd('100') // Allowed because no type check

Better use arrow function so that with the static type check, the compiler discovers the wrong type and does not silently allow for any type by binding.

let boundAdd = (x: number) => add(111, x);
boundAdd(100) // Ok and type check
boundAdd('100') // Error

Use Union Types

Don’t write overloads that differ by type in only one argument position.

//WRONG
interface Moment {
utcOffset(): number;
utcOffset(b: number): Moment;
utcOffset(b: string): Moment;
}

Do use union types whenever possible.

//OK
interface Moment {
utcOffset(): number;
utcOffset(b: number | string): Moment;
}

Note: we didn’t make b optional here because the return types of the signatures differ.

--

--