Home > Enterprise >  Type guards doesn't work when I use them with ReturnType in TypeScript
Type guards doesn't work when I use them with ReturnType in TypeScript

Time:12-04

I have and enum and two functions that returns different data types:

enum Figure {
  Circle,
  Square,
}

const getCircle = () => ({
  figure: Figure.Circle,
  radius: 1
})

const getSquare = () => ({
  figure: Figure.Square,
  width: 1
})

I want to have a union type AnyFigure so that I can use circles and squares in combination. I can do so by defining both types:

type CircleType = {
  figure: Figure.Circle,
  radius: number
}

type SquareType = {
  figure: Figure.Square,
  width: number
}

type AnyFigure = CircleType | SquareType

// this throws compiler error because circles doesn't have width, which is great
const figure: AnyFigure = { figure: Figure.Circle, width: 4 }

This works fine, but I don't want to define return type of every "get figure" function (because in my actual code those are action creators that I use in React's useReducer hook, and there could be quite of few of them, each with different return type).

Thus I tried to use ReturnType instead:

type AnyFigureFromFuncs = ReturnType<typeof getCircle> | ReturnType<typeof getSquare>
// this doesn't throw compile errors, tested on TS 4.5.2
const figure: AnyFigureFromFuncs = { figure: Figure.Circle, width: 4 }

What am I missing here? Thanks.

CodePudding user response:

You can just use as const on enum properties:

enum Figure {
    Circle,
    Square,
  }
  
  const getCircle = () => ({
    figure: Figure.Circle as const,
    radius: 1
  })
  
  const getSquare = () => ({
    figure: Figure.Square as const,
    width: 1
  })

  type AnyFigureFromFuncs = ReturnType<typeof getCircle> | ReturnType<typeof getSquare>
// this doesn't throw compile errors, tested on TS 4.5.2
const figure: AnyFigureFromFuncs = { figure: Figure.Circle, width: 4 }

CodePudding user response:

You can use AnyFigure as return type.

const getCircle = (): AnyFigure => ({
  figure: Figure.Circle,
  radius: 1
})

Or the type itself.

const getCircle = (): CircleType => ({
  figure: Figure.Circle,
  radius: 1
})

CodePudding user response:

Without your showing why you think explicit return types cause a problem with your reducers, it's difficult to answer this in a way that I feel confident satisfies your criteria. Reducers can definitely be quite complex, but sound return types are part of type safety, and can be important in discrimination. Complex, safe programs simply aren't easy to write.

As the question is written, it sounds like you're just asking "How can I make factory functions with one return type (but not have to annotate it every time)?" If that's the case, then you can just use a factory creator like this:

TS Playground link

function createFigureFactory (figure: AnyFigure): () => AnyFigure {
  return () => figure;
}

const getCircle = createFigureFactory({
  figure: Figure.Circle,
  radius: 1,
});

const getSquare = createFigureFactory({
  figure: Figure.Square,
  width: 1,
});

// this is AnyFigure
type AnyFigureFromFuncs = ReturnType<typeof getCircle> | ReturnType<typeof getSquare>;

const figure: AnyFigureFromFuncs = { figure: Figure.Circle, width: 4 };
/* Error:                                                   ^^^^^^^^
   Object literal may only specify known properties, and 'width' does not exist in type 'CircleType'.(2322) */
  • Related