Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Function syntaxes supported by TypeScript

The blog post has been written: https://kentcdodds.com/blog/typescript-function-syntaxes

TypeScript Function Syntaxes

I'm trying to create examples of all the different ways to write functions and function type definitions in TypeScript.

One requirement is these examples must work with strict mode (noImplicitAny, etc) enabled.

If I'm missing anything, please add comments below with examples. I'll eventually put this into a blog post.

Keep in mind that there are TONS of combinations of different syntaxes. I only want to include those which are less obvious combinations or unique in some way.

Function declarations

// inferred return type 
function sum(a: number, b: number) {
  return a + b
}
// defined return type
function sum(a: number, b: number): number {
  return a + b
}

Function Expression

const sum = function sum(a: number, b: number) {
  return a + b
}
const sum = function(a: number, b: number) {
  return a + b
}
const sum = (a: number, b: number) => {
  return a + b
}
const sum = (a: number, b: number) => a + b
const sum = (a: number, b: number): number => a + b
const sum = (a: number, b: number): number => a + b

You can also add a type annotation next to the variable, and then the function itself will assume those types:

const sum: (a: number, b: number) => number = (a, b) => a + b

And you can extract that type:

type MathFn = (a: number, b: number) => number
const sum: MathFn = (a, b) => a + b

Or you can use the object type syntax:

type MathFn = {
  (a: number, b: number): number
}
const sum: MathFn = (a, b) => a + b

Which can be useful if you want to add a typed property to the function:

type MathFn = {
  (a: number, b: number): number
  operator: string
}
const sum: MathFn = (a, b) => a + b
sum.operator = '+'

This can also be done with an interface:

interface MathFn {
  (a: number, b: number): number
  operator: string
}
const sum: MathFn = (a, b) => a + b
sum.operator = '+'

And then there's declare function:

TODO (I'm not sure I understand this myself yet).

Optional/Default params

TODO

Rest params

TODO

Object properties and Methods

Object method:

const math = {
  sum(a: number, b: number): number {
    return a + b
  },
}

Property as function expression:

const math = {
  sum: function sum(a: number, b: number): number {
    return a + b
  },
}

Property as arrow function expression (whith implicit return):

const math = {
  sum: (a: number, b: number): number => a + b,
}

Unfortunately, to extract the type you can't type the function itself, you have to type the enclosing object:

type MathFn = (a: number, b: number) => number

const math: {sum: MathFn} = {
  sum: (a, b) => a + b,
}

Furthermore, if you want to add a property on it like above, then you have to extract the function definition as well:

type MathFn = {
  (a: number, b: number): number
  operator: string
}
const sum: MathFn = (a, b) => a + b
sum.operator = '+'

const math = {sum}

You may have noticed that this example is identical to an example above with only the addition of the const math = {sum}. So yeah, there's no way to do all this inline with the object declaration.

Classes

TODO

Modules

TODO

Overloads

TODO

Generators

TODO

Async

TODO

Generics

TODO

@jensmeindertsma

This comment has been minimized.

Copy link

@jensmeindertsma jensmeindertsma commented Feb 25, 2021

Looks pretty good! What is the reason you decided to include the return type for some and not for others?

@gtkatakura

This comment has been minimized.

Copy link

@gtkatakura gtkatakura commented Feb 25, 2021

Maybe something useful about declaration functions is that we can't define assertion functions as function expressions without explicit types:

const addXY = <T extends any>(obj: T, x: number, y: number)
: asserts obj is (T & { x: number, y: number }) => {
  // Adding properties via = would be more complicated...
  Object.assign(obj, {x, y});
}

const obj = { color: 'green' };

// Assertions require every name in the call target to be declared with an explicit type annotation.
addXY(obj, 9, 4);

Need to be:

function addXY<T>(obj: T, x: number, y: number)
: asserts obj is (T & { x: number, y: number }) {
  // Adding properties via = would be more complicated...
  Object.assign(obj, {x, y});
}

Or:

const addXY: <T>(obj: T, x: number, y: number) => asserts obj is (T & { x: number, y: number }) = (obj, x, y) => {
  // Adding properties via = would be more complicated...
  Object.assign(obj, {x, y});
}

Example of TypeScript: narrowing types via type guards and assertion functions

@kentcdodds

This comment has been minimized.

Copy link
Owner Author

@kentcdodds kentcdodds commented Feb 25, 2021

@jensmeindertsma:

What is the reason you decided to include the return type for some and not for others?

No reason. It was thoughtless 😅 I think I'll default to including it for this.

@franky47

This comment has been minimized.

Copy link

@franky47 franky47 commented Feb 25, 2021

Extracting return types and parameters (arguments) types:

const sum = (a: number, b: number) => a + b

type ReturnTypeOfSum = ReturnType<typeof sum> // number
type ParametersTypesOfSum = Parameters<typeof sum> // [number, number]
@ZackDeRose

This comment has been minimized.

Copy link

@ZackDeRose ZackDeRose commented Feb 25, 2021

perhaps a section on type-asserting functions:

Type Asserting Functions

Given the interface:

interface Foo {
  bar: string;
}

You can write the following function to reasonably assert that x is a Foo:

function isFoo(x: any): boolean {
  return typeof x.bar === string;
}

By using the return signature here x is Foo rather than boolean, we can signal TypeScript to know our parameter's type:

function isFoo(x: any): x is Foo {
  return typeof x.bar === string;
}

function baz(x: any) {
  if (isFoo(x)) {
    // inside this if, ts will give type-safety as it knows x is of Type Foo
    console.log(x.bar);
  }
}

This can be especially helpful in 'splitting' a union type:

function baz(x: Foo | Buzz) {
  if (isFoo(x)) {
    // inside this if, ts will give type-safety as it knows x is of type Foo
    console.log(x.bar);
    return;
  }
  // TS now knows x must be of type Buzz here!
  doSomethingYouCouldOnlyDoToABuzzObj(x);
}
@kentcdodds

This comment has been minimized.

Copy link
Owner Author

@kentcdodds kentcdodds commented Feb 26, 2021

Thanks everyone! Blog post written: https://kentcdodds.com/blog/typescript-function-syntaxes

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment