Skip to content

Instantly share code, notes, and snippets.

@beerose
Created July 31, 2020 14:44
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save beerose/162033d02c730403698becf9d62d8aed to your computer and use it in GitHub Desktop.
Save beerose/162033d02c730403698becf9d62d8aed to your computer and use it in GitHub Desktop.

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

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.

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.

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;
  • 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
}

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;

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;

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.

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

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 {}

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
}

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
Copy link

Great reference doc! ❤️

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