Wrap-up
Chapter 3: TypeScript for real-world applications
Chapters - Table of contents →
You will find below a summary of all the essential knowledge on re-usable components, functions and, types to build professional applications.

Function overloads

  • Each overload define a specific use-case
  • The function implementation should cover all cases (all overloads definitions)
1interface RefObject {
2  readonly current: any
3}
4
5interface MutableRefObject {
6  current: any
7}
8
9interface UseRef {
10  (initialValue: any): RefObject;
11  (): MutableRefObject;
12}
13
14// the implementation must accept none or `any` type of `initialValue`
15// depending on the scenario, the function can return either a
16//   - a `RefObject`
17//   or
18//   - a `MutableRefObject`
19const useRef: UseRef = (ref?: any): MutableRefObject | RefObject => {
20  return {
21    current: null
22  }
23}
24
ā„¹ļø When to use Function overloads?
  • Always try to type a function with union types first
  • If it's not possible, move forward with function overloads.

Generics

  • Leveraging Generics allows building more flexible functions.
1function customDateSort<T extends { createdAt: Date, updatedAt?: Date }>(
2  items: T[]
3) {
4  return items.sort(
5    (a, b) => compareDesc(
6      (a.updatedAt || a.createdAt),
7      (b.updatedAt || b.createdAt)
8    )
9  )
10}
11
12// we added constraints (requiring an array of items with a `createdAt` property)
13//  while keeping the input flexilbe, allowing other properties (`title`)
14const sortedList = customDateSort([
15  { createdAt: new Date(), title: "Blog post 1" },
16]);
17
  • A function can have multiple type arguments
  • A type argument can have a default value
  • We can add a constraint to a type argument by adding a extends.
A type argument value can be inferred from a function argument's type
1// `T` is a type argument, that, if not provided
2//   will be assigned with the type inferred from `initialValue` 
3function useState<T>(initialValue: T) : [
4  value: T,
5  setter: (value: T) => void
6] {
7// ...
8}
9
10// T = Person
11const [value] = useState<Person>({ name: 'John', position: undefined })
12
13// T = { name: string; position: undefined; }
14const [value] = useState({ name: 'John', position: undefined })
15

Manipulate types: type, type helpers

A good practice to keep types strong and maintainable at scale is to avoid declarations duplication and build types on top of each other.
TypeScript is providing a set of type helpers to extract sub-parts or transform existing types:
1import { ButtonProps } from '@material-ui/core'
2
3type UIKitButton = {
4  onClick: Promise<() => boolean> | () => void
5} & Omit<ButtonProps, 'loading' | 'onClick'>
6
Other TypeScript type helpers to keep in mind šŸ’”
  • Pick<T, K>: given a T type and a K type, return a new type only including the K properties of T
    Pick<{ a: string, b: number }, 'a'>gives{ a: string }
  • Required<T> is the opposite of Partial<T>
    Required<{ a?: string, b?: number }, 'a'>gives
    { a: string, b: number }
  • NonNullable<T> removes null and undefined from a givenTtype
    NonNullable<string | null> gives string
  • ReturnType gives the return type of the provided function type
    ReturnType<(a: number, b number) => number> gives number
When should I use interface or type? A rule of thumb šŸ‘
You will need type to achieve the following:
  • Rename an existing type
    type Name = string
  • Transform an existing type (changing properties type)
    type RegisterUserForm = Partial<Omit<User, 'id'>>
  • Create a subset of a type
    type UserWithId = Pick<User, 'id'>
  • Combine multiple types together
    type E = A & B & C
āž”ļø Otherwise, you can use the interface keyword.

Advanced types: Mapped types

All previously seen _type helpers _are heavily relying on TypeScript Mapped types to transform existing types.
Mapped types are based on the _index signature _syntax of TypeScript, used to define more dynamic types by specifying the expected index signature of an iterable/object:
1interface ButtonProps {
2  name: string,
3  // ...
4
5  // index signature
6  [k: string]: string
7}
8
9// `ButtonProps` is now an "open object",
10//   accepting dynamic properties having a string name and value:
11const buttonProps: ButtonProps = {
12   name: 'Bob',
13   city: 'Paris',
14}
15
  • brackets are surrounding the key type, here k
  • the key signature [k: string] is prefixed with a type: string
In short, { [k: string]: string } describes:
An object that can accept any property with a key of type string and value of type string
Mapped types, leveraging index signature syntax, allows to "iterates" over a given type argument in order to transform it:
1// Given a type `Type`, returns all its properties as boolean
2interface ToBoolean<Type> {
3  [Property in keyof Type]: boolean
4}
5
6type UserConfig = ToBoolean<{ acceptedPolicy: string, signedIn: string }>
7// UserConfig type is
8// {
9//   acceptedPolicy: boolean
10//   signedIn: boolean  
11// }
12
We use cookies to collect statistics through Google Analytics.
Do not track
Ā 
Allow cookies