Simple TypeScript Tricks to Scale Your Applications Infinitely

We use TypeScript because it makes development safer and faster.

But in my opinion, TypeScript contains too many indulgences out of the box. They help save a little time for JavaScript developers when switching to TS, but eat up a lot of time in the long run.

I have put together a set of settings and guidelines for a more rigorous use of TypeScript. You need to get used to them once - and they will save a lot of time in the future.

any

The simplest rule that gives my team a lot of profit in the long run: 

Don't use any.

There are practically no situations when you cannot describe a type instead of using any. If I find myself in a situation where I have to write any in a long-term project, I usually find problems in the architecture or it’s legacy code.

Use generic types , unknown or overloads and you can forget about unexpected errors when working with data. Such problems are sometimes difficult to catch and quite expensive to debug.

, any, β€” β„– 3.

strict

TypeScript strict-, , , . TypeScript. , .

strict- undefined is not a function cannot read property X of null. .

-?

, strict .

strict- , . , . , , , .

, β€” . strict- . . . , . strict-!

, . , @ts-ignore TODO. .

// tsconfig.json file
{
    // ...,
    "compilerOptions": {
        // a set of cool rules
        "noImplicitAny": true,
        "noImplicitThis": true,
        "strictNullChecks": true,
        "strictPropertyInitialization": true,
        "strictBindCallApply": true,
        "strictFunctionTypes": true,
        // a shortcut enabling 6 rules above
        "strict": true,
        // ...
    }
}

readonly

β€” readonly.

, , β€” . , Angular- : , .

? readonly .

?

, , , readonly-.

readonly :

// before
export interface Thing {
    data: string;
}

// after
export interface Thing {
    readonly data: string;
}

readonly :

// Before
export type UnsafeType = { prop: number };

// After
export type SafeType = Readonly<{ prop: number }>;

readonly , :

// Before
class UnsafeComponent {
    loaderShow$ = new BehaviorSubject<boolean>(true);
}

// After
class SafeComponent {
    readonly loaderShow$ = new BehaviorSubject<boolean>(true);
}

readonly-:

// Before
const unsafeArray: Array<number> = [1, 2, 3];
const unsafeArrayOtherWay: number[] = [1, 2, 3];

// After
const safeArray: ReadonlyArray<number> = [1, 2, 3];
const safeArrayOtherWay: readonly number[] = [1, 2, 3];

// three levels
const unsafeArray: number[] = [1, 2, 3]; // bad
const safeArray: readonly number[] = [1, 2, 3]; // good
const verySafeTuple: [number, number, number] = [1, 2, 3]; // super
const verySafeTuple: readonly [number, number, number] = [1, 2, 3]; // awesome (after v3.4)

// Map:
// Before
const unsafeMap: Map<string, number> = new Map<string, number>();

// After
const safeMap: ReadonlyMap<string, number> = new Map<string, number>();


// Set:
// Before
const unsafeSet: Set<number> = new Set<number>();

// After
const safeSet: ReadonlySet<number> = new Set<number>();

as const

TypeScript v3.4 const-assertions. , readonly-, . : .

, as const IDE .

Utility Types

TypeScript , .

Utility Types . .

TypeScript . , .

. , :

( repl.it)

import {Subject} from 'rxjs';
import {filter} from 'rxjs/operators';

interface Data {
  readonly greeting: string;
}

const data$$ = new Subject<Data | null>();

/**
 * source$     "Observable<Data | null>"
 *  "null"     filter
 * 
 *  ,   TS    ,    .
 *  "value => !!value"  boolean,      
 */
const source$ = data$$.pipe(
  filter(value => !!value)
)

/** 
 *      
 * 
 *      ,   value  Data.
 *   ,   "wellTypedSource$"  
 */
const wellTypedSource$ = data$$.pipe(
  filter((value): value is Data => !!value)
)

//   ,   :)
// source$.subscribe(x => console.log(x.greeting));

wellTypedSource$.subscribe(x => console.log(x.greeting));

data$$.next({ greeting: 'Hi!' });

:

  • typeof β€” JavaScript .

  • instanceof β€” JavaScript .

  • is T β€” TypeScript, . , TS’ .

:

( repl.it)

// typeof narrowing
function getCheckboxState(value: boolean | null): string {
   if (typeof value === 'boolean') {
       // value has "boolean" only type
       return value ? 'checked' : 'not checked';
   }

   /**
    *     value   β€œnull”
    */
   return 'indeterminate';
}

// instanceof narrowing
abstract class AbstractButton {
   click(): void { }
}

class Button extends AbstractButton {
   click(): void { }
}

class IconButton extends AbstractButton {
   icon = 'src/icon';

   click(): void { }
}

function getButtonIcon(button: AbstractButton): string | null {
   /**
    *  "instanceof" TS ,       "icon"
    */
   return button instanceof IconButton ? button.icon : null;
}

// is T narrowing
interface User {
   readonly id: string;
   readonly name: string;
}

function isUser(candidate: unknown): candidate is User {
   return (
       typeof candidate === "object" &&
       typeof candidate.id === "string" &&
       typeof candidate.name === "string"
   );
}

const someData = { id: '42', name: 'John' };

if (isUser(someData)) {
   /**
    *  TS ,  someData   User
    */
   console.log(someData.id, someData.name)
}

, , , . , TypeScript, , , , . .




All Articles