Categories
TypeScript Best Practices

TypeScript Best Practices — Namespaces, Exceptions, and Type Definitions

Spread the love

TypeScript is an easy to learn extension of JavaScript. It’s easy to write programs that run and does something. However, it’s hard to account for all the uses cases and write robust TypeScript code.

In this article, we’ll look at the best practices to following when writing code with TypeScript, including disallowing the use of TypeScript modules and namespaces.

Also, we don’t need non-null assertions after optional chaining expressions. If we create and use modules, we should use JavaScript modules. Instead of throwing literals, we should throw exceptions.

And, we may want to use one way to declare types instead of 2.

Don’t use Custom TypeScript Modules and Namespaces

Since we have ES6 modules as a standard in JavaScript, we don’t need custom TypeScript modules and namespaces to organize our code.

Instead, we should use standard JavaScript modules with import and export instead.

For instance, instead of writing:

module foo {}
namespace foo {}

or:

declare module foo {}
declare namespace foo {}

We write:

export default foo;

This exports the foo object from a module.

We can also export individual members of a module:

export foo;
export bar;

Don’t Use Non-Null Assertion After Optional Chain Expression

We shouldn’t use non-null assertions after an optional chain expression since they’re opposites of what the optional chain expression does.

By its nature, the expression can return undefined.

For instance, instead of writing:

foo?.bar!;

or:

foo?.bar()!;

We write:

foo?.bar;

or:

foo?.bar();

Don’t Use Non-Null Assertions Using the ! Postfix Operator

Non-null assertions cancel the benefits of strict null-checking mode.

Therefore, we may want to remove the extra ! operators.

For instance, instead of writing:

interface Foo {
  bar?: string;
}

const includesBaz: boolean = foo.bar!.includes('qux');

Instead, we should write:

interface Foo {
  bar?: string;
}

const hasQux: boolean = foo.bar && foo.bar.includes('qux');

Don’t Use Parameter Properties in Class Constructors

Parameter properties are confusing to people new to TypeScript, so we may want to stop the use of that.

It’s a less explicit way to declare and initialize class members.

For instance, instead of writing:

class Foo {
  constructor(readonly name: string) {}
}

We write:

class Foo {
  constructor(name: string) {}
}

Don’t use require() to Import Modules

Now that ES6 modules are standard, we don’t have to use require to import CommonJS modules anymore.

Therefore, instead of writing:

const lib = require('lib');

We write:

import { foo } from 'lib';

Don’t Alias this

Now that we have arrow functions, we don’t need to set this to another variable to keep its value.

For instance, instead of writing:

const self = this;

setTimeout(function() {
  self.foo();
});

We should write:

setTimeout(() => {
  this.foo();
});

Don’t Throw Literals as Exceptions

We should throw Error objects with throw instead of literals since it gives us more information like the line an exception occurred, the stack trace, and the type of error.

For instance, instead of writing:

throw 'error';

We write:

const err = new Error();
throw err;

or:

class BadError extends Error {
  // ...
};
throw new BadError();

Don’t use Type Aliases

We may want to disallow the use of type alias.

Type aliases can alias other types so that we can refer them with simpler names.

For instance, we can write:

type Person = {
    firstName: string,
    lastName: string,
    age: number
};

let person: Person;

instead of:

let person: {
    firstName: string,
    lastName: string,
    age: number
};

It can also act as an interface, like the example before.

Or it can act as a mapping too to let us do quick modifications.

For example, we can write:

type ReadOnly<T> = { readonly [P in keyof T]: T[P] };

type Person = {
  firstName: string;
  lastName: string;
  age: number;
};

For cases where they act like interfaces, maybe we just want to convert them to interfaces instead.

This way, we get one kind of type annotations instead of 2 in our code.

Conclusion

We may want to stick with interfaces for declaring types if our type alias is used like interfaces.

Also, now that we have arrow functions, we don’t need an alias for this .

Instead of throwing literals, we should throw Error objects when we raise exceptions.

By John Au-Yeung

Web developer specializing in React, Vue, and front end development.

Leave a Reply

Your email address will not be published. Required fields are marked *