TypeScript Best Practices

Introduction

As a developer, you should write code easy to read and maintain. To achieve this, we should follow the best practices. In this article, we are going to learn about a few best practices for TypeScript.

Best Practices

  • Turn Strict Checks On
  • Use correct data type
  • Use "let" instead of "var" and "const" for constants
  • General Types
  • "any" or "unknown" type
  • Use the access modifiers for class members
  • Use Strict Equals Operator (===) instead of Equals Operator (==)
  • Use Optional Parameters
  • Use Union Types
  • Remove unused variables and import statement
  • Use correct return type of the function

Turn Strict Checks On

In the TypeScript project, you can add "strict : true" configuration in the tsconfig file. It means "the code should be in strict mode". It helps to prevent mistakes like undeclared variable, the variables didn't used anywhere etc., and also improves the code stability.

Use correct data type

The main advantage of TypeScript is having Data Type annotation. Defining the data types in TypeScript helps to avoid run-time errors. So, you should use the appropriate data types for the variables. Do not use "any" keyword when you know what type of data the variable’s going to have. Whenever you are going to add new variable, then should define it with the appropriate data type.

name: string = "Test";
age: number = 90;
isCorrect: boolean = false;

Use "let" instead of "var" and "const"’ for constants

"var" is used for either a global scope or a local scope declaration. If you define the variable outside of the function/block with "var" type, then it is available to be used anywhere in your script. Same as, when it is defined inside a function, then it is only accessible inside that function.

var name= "Test"; // global scope variable

function fn() {
    var age= 30; // local scope variable
}

The drawback of using "var" is, that it can be redeclared which will leads the errors.

function fn() {
    var age = 80;    
    // logic    
    var age = 90; //It is accepted by TypeScript
}

To avoid this, should use let instead. Let is a blocked scope variable declaration and cannot be re-declared. 

function fn() {
    let age = 80;    
    // logic    
    let age = 90; //It will show error
}

But, you can declare the same variable name in different scopes using "let".

let age = 80;

function fn() {
    let age = 90; //It is accepted
    console.log(age);
}

fn();
console.log(age);

// output:
90
80

const type is similar to let. const is also a blocked scope and cannot be re-declared. The main use of using const is value cannot be updated either (But We could update the value with let). So always use const when you declare a constant.

const age = 90;
const age = 95; //error

General Types

Do not use the types Number, String, Boolean, Symbol, or Object These types refer to non-primitive boxed objects that are almost never used appropriately in the code.

function reverse(s: String): String;

Use the types number, string, boolean, and symbol.

function reverse(s: string): string;

‘any’ or ‘unknown’

Any

If the type of the variable is "any" then we can assign any type of value (string, number, boolean, etc) to the variable. 

let anyVariable: any = 0;
anyVariable = 'Test';
anyVariable = false;

Using "any" will lead to a run time error. 

function invokeCallback(callbackFn: any) {
  callbackFn();
}
invokeCallback(1);  // throws "TypeError: callbackFn is not a function"

Unknown

  • The unknown type comes into the picture to resolve the above run time error.
  • It is similar to any, which means it accepts all types of values (string, number, boolean, etc.). But the difference is, if you are using an "unknown" type then, TypeScript is enforce the type check before using the variable. 
  • In the above example, let’s change the type of callbackFn param from any to unknown.  In this case, the TypeScript throws "Object is of type 'unknown'." error in the callbackFn() statement. Because the callbackFn argument is of type unknown. Now, TypeScript protects from invoking something that might not be a function. To avoid this error, we need to do type checking of the variable before going to use it. 
function invokeCallback(callbackFn: unknown) {
  if (typeof callbackFn === 'function') {
    callbackFn();
  }
}

invokeCallback(1);

So, unknown type is safer than any.

Please refer more details about Any and Unknown type in my other article.

Use access modifiers for class members

When you create the properties in the class is always public. But you should create with appropriate access modifiers (public, protected or private).

  • public: accessible anywhere.
  • private: only accessible inside the class.
  • protected: only accessible inside the class and through subclasses.
class Student {
  protected name: string;
  private mark: number;
  
  constructor(name: string, mark: number) {
    this.name = name;
    this.mark = mark;
  }

  public getMark(){
    return mark;
  }
}

If you want to access the mark, you should use getMark() method.

Use Strict Equals Operator (===) instead of Equals Operator (==)

The strict equals operator (x === y) checks,

  • Both x and y are the same type
  • Both x and y have the same value
let a = 10;
 
a === 10          //true
a === '10'        //false

The equals operator (x == y) , apply the type conversion (to make both values of the same type) before the comparison. For example, if we try to compare a string value to a number value, then string value will be converted first into the number type, and then the comparison will happen.

let a = 10;
 
a == 10         //true
a == '10'       //true (a == parseInt("10"))

So, It is recommended to use strict equals operator for comparison. 

Use Optional Parameters

Don’t write several overload methods that differ only in trailing parameters. 

interface Example {
  check(one: string): boolean;
  check(one: string, two: string): boolean;
  check(one: string, two: string, three: string): boolean;
}

Use optional parameters whenever possible.

interface Example {
  check(one: string, two?: string, three?: string): boolean;
}

Use Union Types

Don’t use overload methods when same argument is with a different type. 

interface Example {
  check(one: string): boolean;
  check(one: number): boolean;
}

You can use union types for the above cases.

interface Example {
  check(one: string | number): boolean;
}

Remove unused variables and import statement

Sometimes we declared/import the variables, but didn't use in the function or blocks. It is not good practice. You should remove the unused variables and import statement from the file.

function add(num1: number, num2: number) : number {
  let result: number; // Here, it is unused. should remove it.
  return num1 + num2;
}

Use correct return type of the function

Don’t use the "any" return type or empty. You should use the correct return type of the function. 

function check() { // Wrong
    return true;
}
function check1() : boolean { // Correct
    return true;
}

Summary

In this article, you have learned about few best practices for TypeScript. Other than the above, there are many best practices developer should follow. The best practices are used to deliver good quality of the code.


Similar Articles