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 not writing empty interfaces.
Also, we should use promises in a useful way.
The any
type also shouldn’t be used.
Also, classes shouldn’t be used as namespaces in TypeScript.
No Empty Interface Declarations
Empty interfaces aren’t very useful. Therefore, we probably don’t want them in our code.
So instead of writing:
interface Foo {}
or:
interface Baz extends Foo {}
We write:
interface Foo {
name: string;
}
or:
interface Baz extends Foo, Bar {}
Extending 2 types inherit members from both types, so that may be useful.
Don’t use the any Type
The any
type allows anything to be assigned to a variable in TypeScript.
Therefore, using the any
type will bypass type checking for anything with that type.
To maximize the usefulness of the TypeScript compiler, we should add some types.
So instead of writing:
const foo: any = 'bar';
We can write:
const foo: string = 'bar';
Likewise, for arrays, if we have:
const arr: any[] = [1, 2];
We should write:
const arr: number[] = [1, 2];
instead.
For parameters, we write:
function greet(names: Array<string>): string {}
instead of:
function greet(names: Array<any>): string {}
We can make our types dynamic with union types, intersection types, and index signatures.
These are much better choices than using any
.
No Extra Non-Null Assertion
We shouldn’t have non-null assertion that doesn’t add any extra value in our code.
For instance, if we have:
const bar = foo!!!.bar;
or:
function baz(bar?: { n: number }) {
return bar!?.n;
}
Then the !
or there’s a nullable operator which cancels out the non-null operator or they’re redundant.
Instead, we should make our non-null operator useful by writing:
const bar = foo!.bar;
Don’t Use Classes as Namespaces
Classes shouldn’t be used as namespaces.
In TypeScript, we can put code other than classes at the top-level, so we can just do that instead of wrapping them in a class.
For instance, instead of writing:
class Foo {
constructor() {
foo();
}
}
or:
class AllStatic {
static num = 42;
static greet() {
console.log('Hello');
}
}
We can write:
class Foo {
constructor() {
foo();
}
bar(){}
}
or:
class Bar {
num = 42;
greet() {
console.log('Hello');
}
}
We should have some instance methods or fields so that we actually need to create a class.
If we only have static variables, then we should create an object literal.
If we have an empty class or a class with only a constructor, then we don’t need the class.
Require Promise-Like Values to Be Handled Properly
We should handle promise-like values properly.
So if we have a promise in our code, we should use await
in an async
function and catch
block to catch errors.
Or we call then
on a promise, then we should call catch
and/or finally
on it to catch errors.
For instance, instead of writing:
const promise = new Promise((resolve, reject) => resolve('foo'));
or:
const foo = async() => {
return 'value';
}
or:
Promise.reject('error').catch();
or:
Promise.reject('error').finally();
We should write:
const promise = new Promise((resolve, reject) => resolve('value'));
await promise;
or:
const foo = async () => {
return 'value';
}
foo().then(
() => {},
() => {},
);
or:
Promise.reject('error').catch(() => {});
or:
Promise.reject('error').finally(() => {});
If our promise returns a value by resolving or rejecting, then we should handle them.
Otherwise, we may not need the promise to be present in our code.
Conclusion
We should use promises in a useful way in our code. Otherwise, we should remove them.
Classes shouldn’t be used as namespaces since we can put code at the top level.
Finally, we shouldn’t have empty interfaces in our code.