質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

88.93%

引数の型によって特定の型の返り値(戻り値)を返す関数(入力された型で出力される型が決まる関数)の作り方

解決済

回答 1

投稿 編集

  • 評価
  • クリップ 1
  • VIEW 156

siic

score 30

型ArgAか型ArgBを受け取って、受け取った型に応じてRetAかRetBの型を返す関数を例とします。

type ArgA = [string, number, boolean]
type ArgB = [number, number]
type RetA = {abc: number}
type RetB = {vw: string, x: {y: number, z: number}}

function sample<T extends ArgA | ArgB>(arg: T): RetA | RetB{
  if (typeof arg[0] == 'string') { //ArgA
    return {abc: 1} //RetA
  }
  return {vw: 'foo', x: {y: 222, z: 333}} //RetB
}

let foo = sample(['a', 2, false]) //RetA に違いないが RetA | RetB
let bar = sample([5, 10]) //RetB に違いないが RetA | RetB


戻り値の型は RetA | RetB ですが、明らかに入力時点で出力される型の予想はつくので、TypeScriptに出力まで推測してもらおうと思い、関数宣言部分に戻り値の型 Result<T> を指定しました。

type Result<T extends ArgA | ArgB> = T extends ArgA ? RetA : RetB
function sample<T extends ArgA | ArgB>(arg: T): Result<T> {
  if (typeof arg[0] == 'string') {
    return {abc: 1} //甲
  }
  return {vw: 'foo', x: {y: 222, z: 333}} //乙
}


そうするとreturn の部分(甲、乙)で「型'‥' を 'Result<T>' に割り当てることができません。(ts2322) 」とvscodeのLSPがエラーを表示しました。

以下のようにas型アノテーションをつけることでエラーを抑えました。

function sample<T extends ArgA | ArgB>(arg: T): Result<T> {
  if (typeof arg[0] == 'string') {
    return {abc: 1} as Result<T>
  }
  return {vw: 'foo', x: {y: 222, z: 333}} as Result<T>
}


型アノテーションを使うのは釈然としませんが、現状このような解決方法しかないのでしょうか?
もっといい方法があればご教授ください。


(質問2)
また、関数の型をオーバーロードを使って定義するように書き換えようとしたところ、うまくいきませんでした。
<*追記:オーバーロードは使わないことにしました。>

type ArgA = [string, number, boolean]
type ArgB = [number, number]
type RetA = {abc: number}
type RetB = {vw: string, x: {y: number, z: number}}
type Result<T extends ArgA | ArgB> = T extends ArgA ? RetA : RetB
type Sample = {
  (arg: ArgA): RetA
  (arg: ArgB): RetB
}
const sample2: Sample = <T extends ArgA | ArgB>(arg: T)=>{ //<-Error
  if (typeof arg[0] == 'string') {
    return {abc: 1} as Result<T>
  }
  return {vw: 'foo', x: {y: 222, z: 333}} as Result<T>
}

vscodeのLSPエラー内容

'<T extends ArgA | ArgB>(arg: T) => Result<T>' を型 'Sample' に割り当てることはできません。
  型 'RetA | RetB' を型 'RetA' に割り当てることはできません。
    プロパティ 'abc' は型 'RetB' にありませんが、型 'RetA' では必須です。ts(2322)


これはどうしてでしょうか。オーバーロードを使って、「型に応じて返す型が決まる関数」の定義方法があるのなら教えていただけませんか? オーバーロードは仕様を表現するだけで型を仕分けするのはコーダーの仕事のようです。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

checkベストアンサー

+1

オーバーロードを使って

自分の印象論かもしれないですが、TypeScriptはコンパイルすると型が消えるという特徴もあるので、オーバーロードはJavaScriptの関数の挙動を表現するために使うもの、という感覚です。

(オーバーロードしても内部では条件分岐して泥臭くなってしまうので、自分でTypeScriptを使って関数を書くなら、無理に1つの関数にまとめずに、引数のパターンごとに関数を分けたほうがやりやすいのではないかと考えます)

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2020/07/23 08:16 編集

    ありがとうございます。オーバーロードは仕様を表現するだけで、型の仕分けはコーダーが手動で行わないといけないということですね。ユニオンされた型が作られるだけであんまり役立つ気がしないので、結局オーバーロードは使わないことにしました。
    今のところジェネリックで捕捉した引数を使って、戻り値に型アノテーションをつける解決法に落ち着きそうです。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 88.93%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る

  • トップ
  • TypeScriptに関する質問
  • 引数の型によって特定の型の返り値(戻り値)を返す関数(入力された型で出力される型が決まる関数)の作り方