Extras for Typescript

From bibbleWiki
Jump to navigation Jump to search

Option Fields for Types

This was a humorous approach to this where the type was

type Props {
   name: string
   gender: "male | female"
   salary?: number
   weight?: number
}

You only want weight for male and salary for female so we can use intersections

type Props {
   name: string
} & (MaleProps | FemaleProps)

type MaleProps {
   gender: "male"
   weight: number
}

type FemaleProps {
   gender: "female"
   salary: number
}

Guards

We can check a type by looking for a property and use the in Operator. E.g.

const printDocument =  (doc: DelimitedDocument | PlaintextDocument) => {
  if("seperator" in doc) {
     printDeliminated(doc)
  }
  else {
     printPlaintext(doc)
  }
};

We can provide a property (__typename) in the types to determine the type and use a predicate. E.g.

export type = Invoice = FinalInvoice | DraftInvoice

export const isTypeFinalInvoice = (invoice: Invoice): invoice is FinalInvoice => {
   return invoice.__typename === "FinalInvoice";
}
export const isTypeDaftInvoice = (invoice: Invoice): invoice is DaftInvoice => {
   return invoice.__typename === "DaftInvoice";
}

Test using asserts not assert

function assertIsNumber(val: any): asserts val is number {
    if(typeof val !== "number") {  
        throw new AssertionError("Not a number"
    }
)

Don't Use Enums

Got this from Matt Pocock. Not a fan and preferred as const object e.g.

const LOG_LEVEL = {
     DEBUG: 'DEBUG',
     WARNING: 'Warning',
     ERROR: 'Error'
} as const

type LogLevel = ObjectValues<typeof LOG_LEVEL>

Union Type

The union type can be for instance

type a = true | "" | 123 | "456"

They cannot have duplicates

// type a = true | "" | 123 | "456" | "456"

Conversion to Unions can be done using T[number]

type TupleToUnion<T extends any[]> = T[number]

type a = TupleToUnion<['', 123, '456','456', true,  '', '', '']>

// type a = true | "" | 123 | "456"

Constraining Types Array

To constrain an array you can do

type myType = number | boolean | string
type TupleToUnion<T extends myType[] = T[number]

But we can do the same with round parameters too. Round was the answer and not

// type TupleToUnion<T extends number | boolean | string[]> = T[number]
// type TupleToUnion<T extends <number | boolean | string>[]> = T[number]

type TupleToUnion<T extends (number | boolean | string)[]> = T[number]

Unions with Types

This example shows how to make a type which has known types and type which is a super set of these known types

// This will hide "sm" and "xs" in the autocomplete
type IconSize = "sm" | "xs" | string
// Better to do
type IconSize = "sm" | "xs" | Omit<string, "xs" | "sm">

Mapped As

Introduction

Needed this for the omit example further down. This is where you can map a value as. For example

type Person = {
   name:string
   age: number
}

type Setters = {
   [K in keyof Person as `set${Capitalize<K>}`]: (value: State[K]) => void
}

// type Setters = { 
//     setName: (value: string) => void 
//     setAge:  (value: number) => void 
// }

Using Intersection types with Mapped As

Here we only want the string keys for the object because we cannot Capitialize with other types. By specifying the intersection only strings are allowed.

type Setters<Person> = {
   [K in keyof Person & string as `set${Capitalize<K>}`]: (value: State[K]) => void
}

Conditional Types

We can breakdown complex types in to simple types. This is useful when the incoming data is more complex than we want

type Unarray<T> = T extends Array<infer U> ? U:T

type Release = Unarry<typeof backlog["releases"]>
type Epic = Unarray<Release["epics"]>

const backlog = {
    releases: [
      name: "Sprint 1", 
      epic: [
       {
           name: "Account Management",
           tasks: [
             { "name": "Single Sign on", Priority.mustHave },
             { "name": "Email Notification", Priority.mustHave },
           ]
       }
      ]
    ]
}

Infer Keyword

The infer keyword is used to represent the outcome on type if a generic. That is a bit of a mouthful so lets give an example. In this example R is a type to represent the return type.

type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any;

In this example R represents the type to for the promise

type PromiseReturnType<T> = T extends Promise<infer R> ? R : T

Some Key examples

Pick

Picks keys to include in an Object

MyPick<T, K extends keyof T> { [k in K] : T[k] }

interface Todo {
  title: string
  description: string
  completed: boolean
}

type TodoPreview = MyPick<Todo, 'title' | 'completed'>

const todo: TodoPreview = {
    title: 'Clean room',
    completed: false,
}

Omit

Omits keys from an Object

type MyOmit2<T, K extends keyof T> = {  [P in keyof T as P extends K ? never: P] :T[P]}

interface Todo {
   title: string
    description: string
    completed: boolean
}

type TodoPreview = MyOmit<Todo, 'description' | 'title'>

const todo: TodoPreview = {
   completed: false,
}

Functional Overloads

Did not know this was possible but it is

Material UI

This is new stuff probably should be with react

// This lets you omit variant as a Prop
import React from "react"
import {Button, ButtonProps } from "matierial-ui/core"

type Props = Omit<ButtonProps, "variant">

const BrandButton: React.FC<Props> = ({children, ...rest }) => {
   return <Button {...rest, children} />
};

export default BrandButton;

CatchError

Got this of wds but put here in case. This shows a generic way to make the catch on one line without a large try catch block. I liked this approach but others not so kind

function catchError<T>(promise: Promise<T>): Promise<[undefined, T] | [Error]> {
    return promise
        .then(data => {
            return [undefined, data] as [undefined, T]
        })
        .catch(err => {
            return [err]
        })
}

const [error, user] = await catchError(getUserById(1))
if (error) {
    console.error('Error getting user:', error)
}
else {
    console.log('User:', user)
}