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 useless comparisons.
Also, we don’t want to write conditionals for things that are always truthy or falsy.
Useless namespace or enum qualifiers can also be removed.
And we should add type annotations to functions before calling them.
Don’t do Equality Comparison Against Boolean Literals
We shouldn’t do equality comparisons against boolean literals.
We can omit it and get the same result.
For instance. the following:
if (someCondition === true) {
}
is the same as :
if (someCondition) {
}
and:
if (someCondition === false) {
}
is the same as:
if (!someCondition) {
}
So we shouldn’t compare against boolean literals.
Don’t Write Conditionals that are Always Truthy or Falsy
We shouldn’t write conditionals that are always truthy or falsy.
For instance, we shouldn’t have code like:
const foo = (arg: 'baz' | 'bar') => {
if (arg) {
}
}
because arg
is either 'bar'
or 'baz'
, so it’s always truthy.
In addition to if
, this also applies to for
, while
and do-while
statements and base values of optional chaining expressions.
For instance, if we have:
const foo = (arg: string) => {
return arg?.length;
}
Since arg
can never be bullish, so we don’t need the ?
.
No Useless Namespace Qualifiers
We shouldn’ write useless namespace or enum qualifiers.
For instance, instead of writing:
namespace A {
export type B = number;
const x: A.B = 3;
}
We write:
namespace A {
export type B = number;
const x: B = 3;
}
since B
is in A
, we don’t need to specify A
.
Likewise, for enums, instead of writing:
enum A {
B,
C = A.B,
}
We write:
enum A {
B,
C = B,
}
Again, B
is in A
, so we don’t have to write A
.
Remove Type Arguments that aren’t Used
We may want to remove explicit type arguments that are the same as the default.
This is because they’re redundant.
For instance, instead of writing:
function foo<T = number>() {}
foo<number>();
We just write:
function foo<T = number>() {}
foo();
Likewise, for function signatures, instead of writing:
class C<T = number> {}
function bar(c: C<number>) {}
We can write:
class C<T = number> {}
function bar(c: C) {}
Don’t Write Useless Type Assertions
If a type assertion doesn’t change the type of an expression, then we shouldn’t write it.
For instance, instead of writing:
const foo = 5;
const bar = foo!;
or:
const foo = <3>3;
or:
const foo = <3>3;
We should write:
const foo = <number>3;
or:
const foo = 3 as number;
or:
const foo = 3 as const;
Changing 3 to the literal type 3 doesn’t change the type.
But changing 3 to the type number
does change the type by making it broader.
Don’t Assign any to Variables and Properties
We shouldn’t change the type of any value to any
or any[]
.
They bypass TypeScript type checks so makes our TypeScript code less robust.
Therefore, instead of writing:
const x = 1 as any;
or:
const [x] = [1] as any[];
We write:
const x = 1 as number;
or:
const [x] = [1] as number[];
Don’t Call any Type Value
We may also look out for any properties or other nested locations.
For instance, instead of writing:
declare const nestedAny: { prop: any };
nestedAny.prop.a();
We may write:
declare const nestedAny: { prop: : { a: () => void } };
Now we can call:
nestedAny.prop.a();
safely since we know nestedAny.prop.a
must be a function.
Conclusion
Writing conditionals for things that are always truthy or falsy are redundant, so we should remove it.
Also, useless type assertions that don’t change the type of value should be removed or changed.
Comparing against boolean literals are also redundant so we should also remove those.