When it comes to software development, TypeScript has gained a unique foothold in new and evolving tech stacks by offering added security layers in JavaScript development—including richer type checks, optional strict compiling, and enhanced code navigation. In other words, TypeScript is simply JavaScript that knows what types of data it’s processing.
Yet, despite its features and benefits, certain TypeScript practices can inculcate problematic patterns, commonly known as anti-patterns—essentially practices that seem helpful initially but prove unproductive or counterproductive in the long run.
Let’s delve into these traps to better understand how we can steer clear from them in our coding journeys.
1. Misunderstanding any
and unknown
Take the use of the any
and unknown
types in TypeScript as an example. The any
type is a flexible type that can literally be anything and hence circumvents TypeScript’s static typing advantages.
A classic misuse of the any
type looks something like this:
let data: any = "I could be anything, even a number!";
data = 1001; //This will not raise an alert
Unknown
, on the other hand, while also a top type like any
, is a safer option as it combines the flexibility of any
without entirely disabling type-checking.
let data: unknown = "I could be anything, maybe a number!";
data = 1001; // But I will raise a type error if not used correctly
As Marijn Haverbeke says in his book Eloquent JavaScript, “Being abstract is something profoundly different from being vague.”
Avoid the traps of loosely typed data by always opting for explicit types. Consider using unknown
if the variable could logically be one of many types, but always try to narrow it down before utilizing it.
2. Overreliance on Type Inference
Overreliance on TypeScript’s type inference capability can also set the stage for anti-patterns. TypeScript does an excellent job of auto-inferencing types based on initial assignments or return types of functions. However, always leaving it up to the compiler might lead to unwanted surprises. As Axel Rauschmayer rightly notes, “When it comes to productivity, readability, and maintainability in coding, explicitness almost always trumps terseness.”
For example, consider this code:
let myVariable = "I am a string";
myVariable = 1001; // Type 'number' is not assignable to type 'string'.ts(2322)
Here, TypeScript inferred the type of myVariable
based on the initial assignment, which can cause issues later if not intended. There’s always a balance to strike between relying heavily on inference and manually specifying types.
In an ideal scenario, a developer must allow TypeScript to infer types where possible but should not shy away from defining explicit types when necessary.
3. Ignoring Return Types of Functions
Another often glanced-over practice is ignoring the return types of functions. By not defining return types explicitly, we might end up with unexpected results.
Consider this GitHub code example from Microsoft’s TypeScript samples:
function greeter(person) {
return "Hello, " + person;
}
let user = "Jane User";
document.body.innerHTML = greeter(user);
In this function, TypeScript infers that the function returns a string. But, what if we made a mistake and forgot to return anything?
function greeter(person) {
"Hello, " + person;
}
let user = "Jane User";
document.body.innerHTML = greeter(user); // Now prints 'undefined'
By explicitly defining the function’s return type, TypeScript would have alerted us to this bug:
function greeter(person): string {
return "Hello, " + person;
}
let user = "Jane User";
document.body.innerHTML = greeter(user);
In software development, it’s critical to not get seduced by the apparent convenience of certain practices. Ultimately, foundational nuances like explicit typing and return types help create maintainable codebases and preemptively address potential errors, adding a robust edge to your TypeScript journey.