Extras for Typescript
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)
}