TypeScriptで引数に応じて戻り値の型を変える

TypeScriptで引数に応じて戻り値の型を変える

November 10, 2021

引数に応じて戻り値の型は変化しない #

例えば以下の関数と実行結果があるとして、age の型は何になるでしょうか?

const getAge = (shouldString: boolean) => {
  return shouldString ? '20' : 20;
};

const age = getAge(true);

引数に true を渡しているため age が文字列としての '20' であることは明白です。

ただし、残念ながら型はそこまで推論できず age の型は 20 | "20" になってしまいます。

Conditional Types を使うだけだとダメ #

Conditional Types を利用すればうまくいきそうですが、残念ながらこれだけだとうまくいきません。

const getAge = <T extends boolean>(shouldString: T): T extends true ? '20' : 20 => {
  return shouldString ? '20' : 20;
};

return 文で返そうとしている型と、戻り値の型として定義している内容が一致しないというエラーが出てしまいます。

「Conditional Types + アサーション」が必要 #

Conditional Types に加えてアサーションを記載することでようやく実現できます。

const getAge = <T extends boolean>(shouldString: T): T extends true ? '20' : 20 => {
  return (shouldString ? '20' : 20) as T extends true ? '20' : 20;
};

const age = getAge(true);

これでようやく age の型は '20' になります。

また可読性を考慮すると、以下のように書くのが良いのではないでしょうか。

type Age<T> = T extends true ? '20' : 20;

const getAge = <T extends boolean>(shouldString: T): Age<T> => {
  return (shouldString ? '20' : 20) as Age<T>;
};

参考:色々なアサーションの記述方法 #

ここまでで内容は以上ですが、以下では参考として、アサーションの書き方で考えられるすべてのパターンを書いておきます。お好みの記述方法を使用してください。

as でアサーション #

const getAge = <T extends boolean>(shouldString: T): T extends true ? '20' : 20 => {
  return shouldString ? ('20' as T extends true ? '20' : 20) : (20 as T extends true ? '20' : 20);
};
const getAge = <T extends boolean>(shouldString: T): T extends true ? '20' : 20 => {
  return (shouldString ? '20' : 20) as T extends true ? '20' : 20;
};
type Age<T> = T extends true ? '20' : 20;

const getAge = <T extends boolean>(shouldString: T): Age<T> => {
  return shouldString ? ('20' as Age<T>) : (20 as Age<T>);
};
type Age<T> = T extends true ? '20' : 20;

const getAge = <T extends boolean>(shouldString: T): Age<T> => {
  return (shouldString ? '20' : 20) as Age<T>;
};

<> でアサーション #

const getAge = <T extends boolean>(shouldString: T): T extends true ? '20' : 20 => {
  return shouldString ? <T extends true ? '20' : 20>'20' : <T extends true ? '20' : 20>20;
};
const getAge = <T extends boolean>(shouldString: T): T extends true ? '20' : 20 => {
  return <T extends true ? '20' : 20>(shouldString ? '20' : 20);
};
type Age<T> = T extends true ? '20' : 20;

const getAge = <T extends boolean>(shouldString: T): Age<T> => {
  return shouldString ? <Age<T>>'20' : <Age<T>>20;
};
type Age<T> = T extends true ? '20' : 20;

const getAge = <T extends boolean>(shouldString: T): Age<T> => {
  return <Age<T>>(shouldString ? '20' : 20);
};