To make code easy to read and maintain, we should follow some best practices.
In this article, we’ll look at some best practices we should follow to make everyone’s lives easier.
Use isNaN to Check for NaN
NaN
doesn’t equal itself, so we can’t use ===
or !==
to check for NaN
.
Instead, we use the isNaN
function to check for it.
So instead of writing:
if (foo === NaN) {}
We write:
if (isNaN(foo)) {}
Invalid Use of void
We should watch for invalid uses of void
.
We shouldn’t mix void
with other types since void
means nothing rather than an explicit type of value.
If we need undefined
, then we should use the undefined
type.
So types expressions like:
Foo | void
should be replaced with:
Foo | undefined
Max Number of Classes Per File
We shouldn’t have too many classes per file.
It’s probably too complex if we do.
We can set the max number of classes per file with a linter.
Max File Line Count
If a file has too many lines, then it’s probably too complex and hard to read.
We can set the max file line count in a file with a linter.
Default Exports
Default exports aren’t as clear, so we should avoid using them.
Named exports are clearer.
So we may want to avoid them.
Default Import
Like default exports, default imports aren’t as clear as named imports.
No Duplicate Imports
We can combine multiple import statements from the same module into one import
statement, so we should do that.
For instance, instead of writing:
import { foo } from 'module';
import { bar } from 'module';
We write:
import { foo, bar } from 'module';
No Mergeable Namespace
We shouldn’t have 2 namespaces with the same name so we don’t have to think about how they’ll be merged together.
Instead, we can just name different namespaces with different names.
For instance, instead of writing:
namespace Animals {
export class Dog {}
}
namespace Animals {
export interface Cat {
numberOfLegs: number;
}
export class Zebra {}
}
We write:
namespace Pets {
export class Dog {}
export interface Cat {
numberOfLegs: number;
}
}
name WildAnimals {
export class Zebra {}
}
No require Imports
Now that ES modules are widely available, we can replace require
with import
.
For instance, instead of writing:
const { bar } = require('foo');
We can use the ES6 module version of the module and write:
import { bar } from 'foo';
Use const
We should use const
instead of let
or var
whenever we can for declaring variables.
This way, they can’t be accidentally be assigned with another value.
For instance, instead of writing:
var x = 1;
let y = 2;
We write:
const z = 3;
Marking Variables as readonly
We should mark variables as readonly
if they’re private and are never modified outside of the constructor.
This way, we won’t be able to accidentally change them.
For instance, instead of writing:
class Foo {
private bar = 1;
}
We write:
class Foo {
private readonly bar = 1;
}
Arrow return Shorthand
If we have an arrow function that only has one statement and returns something, we can make them shorter.
Instead of writing:
() => { return x; }
We write:
() => x
Set Array Types
We should set the types of the array content so that we can restrict the types of values that go into the array.
We can also set generic types to make the array generic.
For instance, instead of writing:
const arr = ['foo', 'bar', 1];
We write:
const arr: string[] = ['foo', 'bar', 'baz'];
Callable Types
If we have an interface or a literal type with just a call signature, then we should write it as a function type.
For instance, instead of writing:
interface SearchFunc {
(source: string, subString: string): boolean;
}
We write:
(source: string, subString: string) => boolean
Conclusion
We should use function types instead of interfaces or literal types with just a function signature.
Arrays should have a type so we can’t put any type of data in it.
We should reduce the complexity of each file.
isNaN
should be used for checking for NaN
.