Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

TypeScript

Prefer @ts-expect-error to @ts-ignore

TypeScript allows you to suppress all errors on a line by placing a single-line comment or a comment block line starting with @ts-ignore immediately before the erroring line. While powerful, there is no way to know if a @ts-ignore is actually suppressing an error without manually investigating what happens when the @ts-ignore is removed.

This means it's easy for @ts-ignores to be forgotten about and remain in code even after the error they were suppressing is fixed. This is dangerous, as if a new error arises on that line, it'll be suppressed by the forgotten about @ts-ignore, and so be missed.

To address this, TS3.9 ships with a new single-line comment directive: // @ts-expect-error.

This directive operates in the same manner as @ts-ignore, but will error if the line it's meant to be suppressing doesn't actually contain an error, making it a lot safer.

💡 Sometimes in very rare cases you may still need @ts-ignore. In that case you need to disable ESLint for that line first; otherwise, ESLint will automatically change it to @ts-expect-error.

Prefer unknown to any

Use unknown for situations where a function doesn't know the incoming type and not when it doesn't care about the type. With better type safety, unknown can help us catch possible errors early on.

🟢 Type guard using unknown:

const validateKey = (key: unknown): key is DictionaryKey => {
    if (['string', 'number'].includes(typeof key)) {
        return true;
    }

    return false;
};

If the above type guard marked key as any, calling key() would not throw.

Prefer direct type assignment to indirect

Prefer directly importing types used in a file rather than accessing them indirectly.

🔴 Indirect assignment:

const doSomething = (networkSymbol: Account['symbol']) => {};

🟢 Direct assignment:

import { NetworkSymbol } from '@suite-common/wallet-config';

const doSomething = (networkSymbol: NetworkSymbol) => {};

Direct assignment may add an import, but it prevents the need to refactor if the NetworkSymbol detaches from Account, is easier to read and makes it easier to navigate to the NetworkSymbol type definition in the editor.

Consider using const assertion on objects instead of TS enums

See https://www.youtube.com/watch?v=0fTdCSH_QEU. TypeScript enums are not native to JavaScript and sometimes behave unpredictably. The desired functionality can be achieved using plain objects. Follow the same naming conventions as you would with enums. Despite the object and the type having the same name, TypeScript is able to tell them apart.

// definition
const AuthMethod = {
    Push: 'Push',
    Sms: 'SMS',
} as const;

type AuthMethod = (typeof AuthMethod)[keyof typeof AuthMethod];

// usage
function doThing(authMethod: AuthMethod): void {
    console.log(authMethod);
}
doThing(AuthMethod.Sms);
doThing('SMS');

Prefer types to interfaces

Just to be consistent. Interfaces offer advanced functionality we don’t really use. Related video: https://www.youtube.com/watch?v=zM9UPcIyyhQ