Skip to content

Typescript: (3) Creating Generic Types

Mithi Sevilla edited this page Mar 30, 2021 · 1 revision

Standard Generic Types

type Coordinate = [number,number, number]
const points: Coordinate[] = [
 [1, 2, 3], [0, 0, 0], [4, 7, 90]
]
// Coordinate[] and Array<Coordinate> 
// declarations are the same

Promise<ReturnType>

const response: Promise<Response> = fetch("https://swapi.dev/api")
response.then(r => console.log(r.ok))

Readonly<Type>

type Action = {
  type: "fetchedName"
  data: string
}

type ImmutableAction = Readonly<Action>

Partial<Type>

type Contact = {
  name: string
  email: string
}

type PartialContact = Partial<Contact>

/* equivalent to 
type PartialContact = {
  name?: string
  email?: string
}
*/

Record<KeyType, ValueType>

type  Result  =  {
  score:  number
  total:  number
}

type  Username  =  "rodj"  |  "janes"  |  "fredp"
type  ResultRecord  =  Record<Username,  Result>

const finalResult:  ResultRecord  =  {
  rodj:  {score:  80, total:  100  },
  janes:  {score:  80, total:  100  }, 
  fredp:  {score:  80, total:  100  },
}

Required<Type>

type  OptionalContact  =  { email?: string, name?: string  }
type  RequiredContact  =  Required<OptionalContact>

const bob:  OptionalContact  =  {} // no error
const fred: RequiredContact = {}
// Type '{}' is missing the following properties from type 'Required<OptionalContact>': email, name

Generic Functions

type  ItemType  =  string  |  number

function firstOrNull<ItemType>(array:  ItemType[]):  ItemType |  null  {
  return array.length ===  0  ?  null  : array[0]
}

console.log(firstOrNull<string>(["Rod",  "Jane",  "Fred"]))
console.log(firstOrNull<number>([1,  71,  45]))
console.log(firstOrNull<number>([]))

IMPORTANT FOR ARROW FUNCTIONS

type  ItemType  =  string  |  number
const findFirst =  <ItemType,  >(array:  ItemType[], match:    ItemType):  ItemType  |  null  =>  {
  return array.indexOf(match) ===-1  ?  null  : array.filter(item => item === match)[0]
}

If you use <ItemType> instead of <ItemType, > This will error

'ItemType' only refers to a type, but is being used as a value here.

Generic Interfaces and Type Aliases

interface Form<T> {
  values: T
}

interface Contact {
  name: string
  email: string
}

const contactForm: Form<Contact> = {
  values: {
    name: "Bob",
    email: "[email protected]"
  }
}

Add validation errors

interface Form<T> {
  values: T,
  errors: {
    [P in keyof T]?: string
  }
}
// [P in keyof T]
// puts keys in the type T into a string literal union
// ie:
// type T = "name" | "email"
// ? properties are optional
// type of errors will be: 
// errors: {name?: string, email: string }

// errors is a requed property because it is not errors?: {...}

Example usage

const contactForm: Form<Contact> = {
  errors: {
    email: "Must be a valid email address"
  },
  values: {
    name: "fred",
    email: "doesnthaveEmailFormat"
  }
}
  • The code below will error because the generated type of the possible properties of errors is only"name" | "email".
  • This is from [P in keyof T] where T is type Contact = { name: string, email: string} so [P in keyof T are the keys of Contact
const contactForm: Form<Contact> = {
  errors: {
    age: "you must enter your age"
  },
  values: {
    name: "fred",
    email: "doesnthaveEmailFormat"
  }
}

Here's the type alias version

// interface
interface Form<T> {
  values: T,
  errors: {
    [P in keyof T]?: string
  }
}

// type
type Form<T> = {
  values: T,
  errors: {
    [P in keyof T]?: string
  }
}

Generic Classes

class  List<ItemType>  {
  private items:  ItemType[]  =  []
  add(item:  ItemType)  {
    this.items.push(item)
  }
}

const numberList =  new  List<number>()
numberList.add(1)
// numberList.add(null) // this will error