TypeScript brought me a lot of useful features like the ability to rename variables by reference in multiple places, spot errors in the code before even running the app, and a lot more.

Here are a few small things I do that make a big difference in code maintainability and readability.

Use primitive types

Use string, number, boolean on your variable types. Don’t use non-primitive types like String or Number. Think about this: would you assign a number max = new Number(10) or max = 10?

Wrong

let max: Number

✔️ Right

let max: number

Use the type void

When your function doesn’t return any value, you can use the type void.

Not returning a value can be bad if you want to have pure functions and avoid side-effects. In that case you should avoid void.

But, in some cases, like callbacks, you can use this to prevent wrong usage of a function.

Wrong

let log: (message: string) => any
log = (message) => {}

const result = log('') // allowed

✔️ Right

let log: (message: string) => void
log = (message) => {}

const result = log('') // syntax error

Place types where they belong

Place types close to where they are being used. That’s probably the right home for them.

Wrong

Create a types.ts file for your entire app. A file like this is going to get big and hard to maintain.

✔️ Right

Place types in their proper file and context. For example, if you have a file that fetches data from an API and returns an object, the type definition for that data should be in the same file.

Prefer implicit types over explicit types

According to Clean Code, comments that simply repeat what the variable is saying are just noise. The same goes for types: there’s no point in duplicating the information.

Wrong

const message: string = 'hello world'

✔️ Right

const message = 'hello world'

Don’t overuse the type any

The type any can be really useful or easier in many scenarios, but if you keep overusing it you’re giving up all of TypeScript’s advantages.

Once you use an any, the any-ness can leak into other parts of your code and really wreck type-safety.

Wrong

Use any everywhere.

✔️ Right

Use any only when really needed. One example where it could be ok is in a third party API that might change or might return way more information than you need.

Prefer async/await over then

When using promises you can make your code linear with the async/await feature, this makes the code a lot easier to read.

Wrong

foo().then(bar).then(baz)

✔️ Right

await foo()
await bar()
await baz()

Avoid export default

You can name a default export anything. This is sometimes really bad for me because it’s hard to remember what I named the same import in another place— I have to go back and check it if I want to keep consistent. And, as if this weren’t hard enough, if you refactor, it will not rename the imported names.

Wrong

export default Foo

import FooComponent from './foo'

✔️ Right

export class Foo {}

import { Foo } from './foo'

Clean code is all about keeping the code simple, easy to read, easy to change.

Disclaimer: I’m using ❌ Wrong and ✔️ Right just to make it clearer what is the preferred approach, it’s not a rule that says that the approach is absolutely right or wrong.


Other Resources