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);
};