TypeScript Gotchas
Ed Bentley
Posted on March 14, 2020
TypeScript can be a great language to work with. If you're starting a new mid to large project in JavaScript, I would certainly recommend using TypeScript at the moment. Knowing your code is correctly typed brings a lot of confidence when it comes to making changes.
However in order to get this great experience, I always have a few "gotchas" in the back of my head while writing TypeScript. These are things to watch out for, which can cause runtime issues if you're not careful.
This can provide us with a "sound" codebase, even though TypeScript is inherintly an "unsound" type system.
These examples have been tested with TypeScript v3.8.3
1. Beware JSON.parse
and .json()
const obj = JSON.parse('{ "x": 5 }');
obj.hello(); // uh-oh! obj.hello is not a function
const data = await fetch("https://pokeapi.co/api/v2/pokemon/1/").then(res => res.json());
data.hello(); // same again!
Here obj
and data
are returning the type any
, even in strict mode, and so TypeScript doesn't warn you about these runtime errors.
Solution: you should decode anything that you don't know the type of. I recommend bringing in a library like io-ts for the job. It's also really useful for query parameters.
2. Beware record types
type MyObj = Record<string, string>;
const myObj: MyObj = {};
const result = myObj.hello;
What's the type of result
? TypeScript tells me it's string
, but actually it's undefined
!
I still find Record
to be useful in some cases but you really need to be careful with it. You could also consider using something like fp-ts lookup in order to be extra careful.
3. Beware type assertions
type MyObj = { hello: () => void };
const myObj = {} as MyObj;
myObj.hello(); // uh-oh! myObj.hello is not a function
Just avoid as
and angle bracket type assertions as much as you can.
4. Beware catch
try {
throw undefined;
}
catch (e) {
e.hello(); // uh-oh! Cannot read property 'hello' of undefined
}
Here e
has type any
so no type errors are reported. Interestingly if you try to annotate e
like
catch (e: Error) {
TypeScript won't let you do that which is good. But without it it's still returned as any
. 🤷
Watch out for .catch
on a Promise
too.
5. Don't use any
or !
By now this should go without saying. Using any
breaks down the point of bringing in TypeScript and you loose all of your confidence everything is sound and runtime error free.
!
can be very convenient but it's best avoided. My only exception is in test code.
Avoid things like this
function lowercaseOptional(val?: string) {
return val!.toLocaleLowerCase();
}
and prefer explicitly handling the undefined
case like this
function lowercaseOptional(val?: string) {
if (!val) return "";
return val.toLocaleLowerCase();
}
You can ensure any
and !
aren't used by setting up the eslint rules no-explicit-any and no-non-null-assertion.
6. Be careful with libraries!
There are some libraries which served their purpose brilliantly in JavaScript. But in TypeScript, it's simply not feasible to keep using the same API while also keeping your code sound.
This example is taken straight from the lodash doc and converted to TypeScript:
function square(n: number) {
return n * n;
}
var addSquare = _.flow([_.add, square]);
addSquare(1, 2);
What's the return type of addSquare
? any
! 😩
Honourable mention - arrays
const array: string[] = [];
const val = array[1];
TypeScript tells us val
is of type string
, when really it's undefined
. However this is a gotcha in most programming languages, and it really wouldn't be practical to check for optionals in something like a for
loop. But it's good to be aware of.
Do you have any more "gotchas" I'm missing? Please share in the comments below!
Posted on March 14, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
November 29, 2024