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 type assertions and having explicit type annotations.
Enforce Consistent Usage of Type Assertions
Type assertion styles should be consistent across our project.
Type assertions are also referred to as data type casting in TypeScript.
However, they’re technically different.
We can either use the as
keyword or <>
to add type assertions to our values.
For instance, we can either stick with:
let x = "foo" as string;
or:
let x = <string>"foo";
const
is also allowed with the rule. It’s available since TypeScript 3.4 and it allows us to make something read-only.
For instance, we can write:
let x = "foo" as const;
or:
let x = <const>"foo";
Consistent Type Definitions with interface or type
In TypeScript, we can define interfaces for annotating types.
Also, we can create type alias with the type
keyword to do the same thing.
It’s a good idea to be consistent with what we use when we define types.
For instance, we can either stick with interfaces:
interface Foo {
a: string;
b: number;
}
or type aliases:
type Foo = {
a: string;
b: number;
};
Require Explicit Return Types on Functions and Class Methods
It’s a good idea to add return type annotations for functions and class methods.
This way, we know what each function or method returns.
For instance, instead of writing:
function foo() {
return 'bar';
}
We can write:
function foo: string() {
return 'bar';
}
Likewise, with class methods, instead of writing:
class Foo{
method() {
return 'bar';
}
}
We write:
class Foo{
method(): string {
return 'bar';
}
}
If our method returns nothing, we use the void
return type:
function foo(): void {
return;
}
or:
class Foo{
method(): void {
return;
}
}
Require Explicit Accessibility Modifiers on Class Properties and Methods
If we leave out the accessibility modifiers in classes, then it may be hard for people to understand whether a class property or method is accessible or not.
By default, if we leave them out, the class member is public.
So we may also want to restrict access to some members in most cases.
Therefore, we should include them.
So instead of writing:
class Foo {
foo() {
console.log("foo");
}
bar() {
//...
}
}
We may want to restrict access to some members by writing:
class Foo {
foo() {
console.log("foo");
}
private bar() {
//...
}
}
This way, the TypeScript compiler will give us an error if we try to compile it.
Access modifiers include public
, private
, readonly
and protected
.
A public
member is available to everything.
A private
member is only available within the class.
A readonly
member is public but it’s read-only.
A protected
member is only available within the class or any child class.
Require Explicit Return and Argument Types on Exported Functions and Classes’ Public Class Methods
To make working with functions and public methods of classes easier, we should add explicit argument and return types so that we can get autocomplete and errors when we work with them.
For instance, instead of writing:
export function test() {
return;
}
We write:
export function test(): void {
return;
}
And instead of writing:
class Foo {
method() {
return;
}
}
We write:
export class Foo {
method(): void {
return;
}
}
Likewise, with argument types, we write:
export class Foo {
method(foo: string): string {
return foo;
}
}
and:
export function test(foo: string): string{
return foo;
}
Now we don’t have to guess or look up the types of exported classes and functions.
Conclusion
We should annotate return and argument types so don’t have to look up the types of functions and class methods that are exported.
Consistent usage of type assertions notation may also be a good idea.
Also, it may be a good idea to have consistent usage of interface
or type
to create new types.