Skip to content

Instantly share code, notes, and snippets.

@NoFishLikeIan
Created July 25, 2019 09:20
Show Gist options
  • Save NoFishLikeIan/194e134860e9f2c3ad17702413847c9e to your computer and use it in GitHub Desktop.
Save NoFishLikeIan/194e134860e9f2c3ad17702413847c9e to your computer and use it in GitHub Desktop.
Intro to advanced types ts

Typescript for the young and free

Some "advanced" types

The backend returns some optional fields but you want to write a function that makes those optional fields nullable. Why? No apperent reason.

type Response = {
  name: string;
  age?: number;
};

The usefulness of intersection (&)

First step is to write a key mapper, i.e. I want a generic that takes two objects and returns a map of the common keys that don't have the same type, so you can use &

type MappedC<A, B> = {
  [K in keyof A & keyof B]: A[K] extends B[K] ? never : K
};

// example

type M = MappedC<{ a: number }, { a: string }>; // {a: 'a'}
type O = MappedC<{ a: number }, { a: number }>; // {a: never}
type D = MappedC<{ a: number }, { b: number }>; // {}

The default generics that typescript gives you (e.g. Required)

Second step is finding all the optional keys!

type Optional<T> = MappedC<T, Required<T>>[keyof T];

type OptionalKeys = Optional<Response>; // 'age'

The - and ternary operator

The - removes properties such as readonly or ?. For example do you want to have the same but writable and optionals? You can just do:

type Writeable<T> = { -readonly [P in keyof T]-?: T[P] };

The ternary operator on the other hand is always coupled with extends and allows you do "split" a type based on another one.

So now the final type is:

export type FromOptionalToNullable<T extends {}> = {
  [K in keyof T]-?: K extends OptionalKeys<T> ? T[K] | null : T[K]
};

type ParsedResponse = FromOptionalToNullable<Response>; //

type ParsedResponse = {
  name: string;
  age: number | number;
};

More crazy shit: infer and extends

The extends is basically a conditon check on whether the second element of the condition is containd in the first. It is often used as a ===; The infer is a way to assign a name to an unknown type and let TS do the rest (extremely cool and powerful btw);

type Infer<O> = O extends { whatIsThis: infer A } // Returns the type of `whatIsThis` in an object
  ? A
  : never;

type WhatIsThis = Infer<typeof { whatIsThis: "findMe!" }>; // string

// Functions
type InferArgument<F> = F extends (...input: infer I) => infer R ? [A, R] : never

type Input = InferArgument<typeof (first: number, second: string) => true> // [[number, string], booolean]

// Promises
type PromiseInfer<I> =
  I extends Promise<infer G>
    ? G
    : never

Some playground here

The point of the thing

Now basically I don't care about what the type in the "backend" is, I want to write a function I can start from the signature and be let typescript guide me in the process.

const parseResponse = (resp: Response): ParsedResponse => {
  const parsed = Object.entries(resp).reduce<ParsedResponse>(
    (p, [k, v]) => ({ ...p, [k]: v || null }),
    {}
  );

  return parsed;
};

Some more fun, let's say I want to create a Curry type

type Head<T extends any[]> = T extends [any, ...any[]] ? T[0] : never; // Find the type of a tuple head

type Tail<T extends any[]> = ((...t: T) => any) extends ((
  _: any,
  ...tail: infer TT
) => any)
  ? TT
  : [];

type HasTail<T extends []> = T extends ([] | [any]) ? false : true;


type Curry<P extends any[], R> =
  <T extends any[]>(...args: T) =>
    HasTail<P> extends true ?
    ? Curry<Tail<T>, R>
    : R

You can get lost in this stuff man. But if you want to know more just check this out

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