switchかなんかで条件によって異なる値を返す関数の型の書き方

この間やっててハマったので、意外とハマる人多いのでは?ということでメモ

前提⚠️

  • typescript v4.0.2

■ 前提となる変数たち

type valueOf<T> = T[keyof T];

const KEY = {
  foo: 'foo',
  bar: 'bar'
} as const;
type Key = valueOf<typeof KEY>;

const VALUE = {
  foo: 'foo',
  bar: 'bar'
} as const;
type Value = valueOf<typeof VALUE>;

こんな場合の話🙆🏻♂️‍

const getFooOrBar = (key: Key) => {
  switch (key) {
    case KEY.foo:
      return VALUE.foo;
    case KEY.bar:
      return VALUE.bar;
  }
};

const foo = getFooOrBar(KEY.foo);

このとき、foofooリテラル型になっていてほしい、というかなるのでは?と思ったが、実際は以下のようになる

const foo: "foo" | "bar"

僕がハマったとき、実装がもう少し複雑だったので問題がなにかに気づくのに時間がかかったし、端的に言って鬼ハマリして時間をドバドバとほとんど湧いては捨てられる温泉のように無駄にした♨️

どうやんの?😕

ここに書いてあります🦸♂️‍ https://stackoverflow.com/questions/58673034/type-inference-from-switch-case-return-with-typescript

公式はこいつです🙋 https://www.typescriptlang.org/docs/handbook/functions.html#overloads

なるほど、たしかにopenapiとかで自動生成されたコードで見たことあるわ、こいつ🤔
というわけで、functionのoverloadsを使います

function getFooOrBar(key: Extract<Key, 'foo'>): Extract<Value, 'foo'>;
function getFooOrBar(key: Extract<Key, 'bar'>): Extract<Value, 'bar'>;
function getFooOrBar(key: Key) {
  switch (key) {
    case KEY.foo:
      return VALUE.foo;
    case KEY.bar:
      return VALUE.bar;
  }
}

const foo = getFooOrBar(KEY.foo); // const foo: "foo"

■ curry化されていて、返す関数の型をやるとき

はキモいですがこんな感じ🐛

const createFooOrBarGetter = () => {
  function getFooOrBar(key: Extract<Key, 'foo'>): Extract<Value, 'foo'>;
  function getFooOrBar(key: Extract<Key, 'bar'>): Extract<Value, 'bar'>;
  function getFooOrBar(key: Key) {
    switch (key) {
      case KEY.foo:
        return VALUE.foo;
      case KEY.bar:
        return VALUE.bar;
    }
  }
  return getFooOrBar;
};

■ この関数をexportしたいとき

は全部exportしてね🚛

export function getFooOrBar(key: Extract<Key, 'foo'>): Extract<Value, 'foo'>;
export function getFooOrBar(key: Extract<Key, 'bar'>): Extract<Value, 'bar'>;
export function getFooOrBar(key: Key) {
  switch (key) {
    case KEY.foo:
      return VALUE.foo;
    case KEY.bar:
      return VALUE.bar;
  }
}

良かったですね🙆🏻♂️‍