Annotations With Functions And Objects

Annotation and Inference for functions

If you recall, we showed a type annotation with functions:

const logNumber: (i: number) => void = (i: number) => console.log(number);

However, (i:number) => void isn't type annotation for a function. It's actually type annotation for a variable. Type annotation for a function focuses around the right-hand side of that = sign.

Type annotation for functions tells TypeScript the arguments' types and the return value type. Here's the syntax:

const add = (a: number, : number): number => a + b;

Note: TypeScript cannot tell you if the logic of your function body is accurate. If you return a - b by accident, it won't tell you. But if you return 'some string value', it will throw an error.

Type inference for functions can only infer the return value type. It cannot infer the arguments' types.

Best practice: We want to use type annotation for arguments and return values. Even though type inference works for return values, it's not a good idea to use it. That's because we could make a typo, giving us an inaccurate inference.

// We forgot to type `return`, so the inferred return type is `void`
const subtract = (a: number, b: number) => {
  a - b;
};

void and never

Some functions don't return anything but instead perform side effects. To type the return value, just use void.

const logger = (message: string): void => console.log(message);

On very rare occasions, you may expect a function to never return anything. In this case, you use the never type.

// Errors break out of the function without returning anything
const throwError = (message: string): never => {
  throw new Error(message);
};

Note: If you expect your function to return a value at least sometimes, do not use never. Use the return value type.

const throwError = (message: string): string => {
  if (!message) {
    throw new Error('no message provided');
  }
  return message;
};

Type annotating destructured arguments

Sometimes it's nice to be able to destructure arguments in your functions. To annotate these destructured arguments, just place the destructuring on the left-hand side and the types on the right-hand side:

const logWeather = ({
  date,
  weather,
}: {
  date: Date;
  weather: string;
}): void => {
  console.log(date);
  console.log(weather);
};

Annotation and Inference for Objects

Just like how we annotate a destructured argument in a function, type annotating an object is pretty much the same when it comes to destructuring an object.

const obj = {
  foo: 1,
  bar: 'hello',
};

const { foo, bar }: { foo: number; bar: string } = obj;

Last updated