profileRyan KesPGP keyI build stuffEmailGithubTwitterLast.fmMastodonMatrix

TypeScript Functions

TypeScript 4.0

Variadic Tuple Types

Pre 4.0 situation

Consider the following function using Tuples:

function concat(arr1, arr2) {
  return [...arr1, ...arr2]
}

The only way to type this in TypeScript used to be:

function concat(arr1: [], arr2: []): [];
function concat<A>(arr1: [A], arr2: []): [A];
function concat<A, B>(arr1: [A, B], arr2: []): [A, B];
function concat<A, B, C>(arr1: [A, B, C], arr2: []): [A, B, C];
function concat<A, B, C, D>(arr1: [A, B, C, D], arr2: []): [A, B, C, D];
function concat<A, B, C, D, E>(arr1: [A, B, C, D, E], arr2: []): [A, B, C, D, E];
function concat<A, B, C, D, E, F>(arr1: [A, B, C, D, E, F], arr2: []): [A, B, C, D, E, F];)

function concat<A2>(arr1: [], arr2: [A2]): [A2];
function concat<A1, A2>(arr1: [A1], arr2: [A2]): [A1, A2];
function concat<A1, B1, A2>(arr1: [A1, B1], arr2: [A2]): [A1, B1, A2];
function concat<A1, B1, C1, A2>(arr1: [A1, B1, C1], arr2: [A2]): [A1, B1, C1, A2];
function concat<A1, B1, C1, D1, A2>(arr1: [A1, B1, C1, D1], arr2: [A2]): [A1, B1, C1, D1, A2];
function concat<A1, B1, C1, D1, E1, A2>(arr1: [A1, B1, C1, D1, E1], arr2: [A2]): [A1, B1, C1, D1, E1, A2];
function concat<A1, B1, C1, D1, E1, F1, A2>(arr1: [A1, B1, C1, D1, E1, F1], arr2: [A2]): [A1, B1, C1, D1, E1, F1, A2];

Post 4.0 situation

  1. Tail example

    Consider the following example:

    function tail(arg) {
      const [_, ...result] = arg
      return result
    }

    TypeScript 4.0 bring 2 changes here.

    1. Changes

      1. Generic spreads in tuple types

        The spreads in tuple type syntax to be generic:

        function tail<T extends any[]>(arr: readonly [any, ...T]) {
          const [_ignored, ...rest] = arr
          return rest
        }
        
        const myTuple = [1, 2, 3, 4] as const
        const myArray = ["hello", "world"]
        
        // type [2, 3, 4]
        const r1 = tail(myTuple)
        
        // type [2, 3, 4, ...string[]]
        const r2 = tail([...myTuple, ...myArray] as const)
      2. Rest elements can occur anywhre in a tuple

        Rest elements can occur anywhere in a tuple, not just at the end:

        type Strings = [string, string]
        type Numbers = [number, number]
        
        // [string, string, number, number, boolean]
        type StrStrNumNumBool = [...Strings, ...Numbers, boolean]
  2. Concat example

    Note that in cases when we spread in a type without a known length, the resulting type becomes unbounded as well, and all the following elements factor into the resulting rest element type.

    type Strings = [string, string]
    type Numbers = number[]
    
    // [string, string, ...Array<number | boolean>]
    type Unbounded = [...Strings, ...Numbers, boolean]

    Therefore the concat() example can be typed with:

    type Arr = readonly any[]
    
    function concat<T extends Arr, U extends Arr>(
      arr1: T,
      arr2: U
    ): [...T, ...U] {
      return [...arr1, ...arr2]
    }

Examples

Parameter annotations

function tralala(tra: number, la: number) {}

Return type annotation

interface Foo {
  bar: string
}

function tralala(): Foo {
  return { bar: "tralala" }
}

Optional Parameters

function foo(bar: number, optional?: string) {}
function foo(bar: number, optional: string = "This is an optional string") {}

Overloading

This is for documentation purposes but imho just makes things more confusing.

// Overloads
function padding(all: number)
function padding(topAndBottom: number, leftAndRight: number)
function padding(top: number, right: number, bottom: number, left: number)
// Actual implementation that is a true representation of all the cases the function body needs to handle
function padding(a: number, b?: number, c?: number, d?: number) {
  if (b === undefined && c === undefined && d === undefined) {
    b = c = d = a
  } else if (c === undefined && d === undefined) {
    c = a
    d = b
  }
  return {
    top: a,
    right: b,
    bottom: c,
    left: d,
  }
}

padding(1) // Okay: all
padding(1, 1) // Okay: topAndBottom, leftAndRight
padding(1, 1, 1, 1) // Okay: top, right, bottom, left

padding(1, 1, 1) // Error: Not a part of the available overloads

Declaring Functions

Use this to declare function type without providing implementation:

type LongHand = {
  (a: number): number
}

type ShortHand = (a: number) => number

Overloading is supported as well:

type LongHandAllowsOverloadDeclarations = {
  (a: number): number
  (a: string): string
}

Related