Skip to content

Instantly share code, notes, and snippets.

@beerose
Created Jul 31, 2020
Embed
What would you like to do?

Table of contents

  1. Type vs. Interface
  2. Enums
  3. Object, object & {}
  4. Type guards
  5. Type assertions
  6. Implicit vs. explicit typing
  7. Generics vs. any vs. unknown
  8. File Names
  9. Casing and naming conventions
  10. Comments
  11. Redux

Type vs. Interface

What's the difference?

Source: https://stackoverflow.com/questions/37233735/typescript-interfaces-vs-types/37233777

When to use interface?

  • Whenever you can 😉 Why?
    • Declaration merging,
    • Extending is safer than intersections. You won't accidentally override a property.

When to use type?

  • When you want to alias something in the scope of the particular file.
  • When you are not exporting the particular type.
  • You need a union type.
  • When you need mapped types.

Enums

Don't use enums.

Why?

  • Come with a huge variation surface (numeric, string-based, manually counted, automatically counted, inline, dynamic, one-directional, bi-directional).
  • Create bloated objects in runtime.
  • Produce unexpected results because of merging (two of the same name can live in one scope)
  • Consider the following example:
enum Status { 
  a = 1,
  b = 2,
  c = 3,
}

const foo = (x: Status)  => console.log(x);
foo(100); // ok, no error

What to use instead?

  • String literal types: type State = "loading" | "error" | "initial"
  • Numeric literal types: type State = 1 | 2 | 3

You can read more about how enums work here.

Object, object & {}

Don't use {}

Why?

  • {} is the empty type and is assignable from any non-null/non-undefined type.
  • It allows extra properties: const obj: {} = { size: 10 } // OK.
  • It allows primitives: const obj: {} = "hello" // OK.

Don't use Object

Why?

Don’t ever use the types Number, String, Boolean, Symbol, or Object These types refer to non-primitive boxed objects that are almost never used appropriately in JavaScript code. https://www.typescriptlang.org/docs/handbook/declaration-files/do-s-and-don-ts.html#number-string-boolean-symbol-and-object

Use object or <T extends object>

Bad

const foo = (x: Object) => null;
const foo = (x: {}) => null;

Good

const foo = (x: object) => null;
const foo = <T extends object>(x: T, key: keyof T) => null;

Type guards

  • Use type guards wherever for every predicate that is checking the type. (Prefer type guard instead of usual predicates where applicable.)
  • Use unknown for the parameter type.
  • It allows TypeScript to narrow the type correctly.

Bad

const isUser = (x: unknown) => x && x.name && x.age;

// x is of type unknown
if (isUser(x)) {
  // x is still of type unknown
}

Good

const isUser = (x: unknown): x is User => x && x.name && x.age;

// x is of type unknown
if (isUser(x)) {
  // x is of type User
}

Type assertions

Use as for type assertions. Why?

  • Consistency,
  • <type>value is often associated with type casts in other languages.

Bad

const env = <string>process.env.NODE_ENV;

Good

const env = process.env.NODE_ENV as string;

Implicit vs. explicit typing

Let the TypeScript compiler infer as much as possible and avoid defining types when it is unnecessary.

Bad

const trim = (x: string): string => x.trim();

const x: number = 10;

Good

const trim = (x: string) => x.trim();

const x = 10;

Generics vs. any vs. unknown

Type parameters should be used to enforce constraints between types.

  • capture the relationship between function arguments and its return type
  • capture the relationship between the class type and its properties and methods

Type parameters should always describe a relationship.

Bad

// type parameter is only used once
declare const doSomething = <T>(x: T): void;

// type parameter is not used to define any parameter
declare const parse = <T>(): T;
const x = parse<string>();

// any is unsafe and "turns off" type checking
declare const parse = (): any;
const x = parse();

Good

declare const doSomething = (x: unknown): void;

declare const parse = (): unknown;
const x = parse() as number;

Use one-letter type parameters names

Bad

type Transform<InputProps, OutputProps> = (props: InputProps) => OutputProps

Good

type Transform<A, B> = (props: A) => B

Why?

  • It makes the type reusable,
  • Expressed the generic nature of the function.

If you have too many type parameters that one-letter names are hard to read, then you have too many type parameters.

File names

Use PascalCase for React components (if the file's main export is React component), and camelCase otherwise.

Casing and naming conventions

Use camelCase for variable and function names.

Bad

const FooBar = '';

Good

const fooBar = '';

Use PascalCase for classes, type aliases, and interfaces and camelCase for members.

Bad

type foo = string;
interface bar {
  Name: string
}
class component { }

Good

type Foo = string;
interface Bar {
  name: string
}
class Component { }

Don't prefix interfaces with I.

Bad

interface IRequest {}

Good

interface Request {}

Comments

Make sure your comments are meaningful.

Bad

// we are setting counter to 0
const counter = 0;

Good

const counter = 0;

If you are documenting a function behavior consider using JSDoc format.

Example:

/**
 * This is a function.
 *
 *
 * @example
 *
 *  foo('hello')
 */

function foo(n: string) { return n }

It's okay to leave todo comments.

interface State {
  items: any; // todo — dependent on X/requires Y to type correctly
}

Redux

How to type actions and actions creators?

// service/types.ts
export const INSERT_REQUEST = 'INSERT_REQUEST' as const;
export const UPDATE_REQUEST = 'UPDATE_REQUEST' as const;

const insert = (payload: string) => ({
  type: INSERT_REQUEST,
  payload,
})

const update = (payload: { id: number }) => ({
  type: UPDATE_REQUEST,
  payload,
})

export type ActionTypes = ReturnType<typeof insert | typeof update>;

How to type connect HOC?

import { connect, ConnectedProps } from 'react-redux'

const mapState = (state: RootState) => ({
  tables: state.tables
})

const mapDispatch = {
  setTableName: (name: string) => ({ type: 'SET_TABLE_NAME', name })
}

const connector = connect(mapState, mapDispatch)

interface Props extends ConnectedProps<typeof connector> {
  tableName: string
}

const MyComponent: React.FC<Props> = props => (
  <div>
    {props.tableName}
    <input onChange={e => props.setTableName(e.target.value)} type="text" />
    {props.tables.map(t => (<p>t.name</p>))}
  </div>
)

export default connector(MyComponent)

How to type reducers?

Follow this documentation

How to use TypeScript with Redux Thunk?

Follow this documentation.

@lautapercuspain

This comment has been minimized.

Copy link

@lautapercuspain lautapercuspain commented Aug 1, 2020

Great reference doc! ❤️

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment