1. test 1
  2. test 2
  3. test 3
  4. test 4
  5. test 5
コース一覧
TypeScript道場 fleama開発室の10問
汎用関数に型を付けよ

TypeScript道場 fleama開発室の10問

TypeScript入門で身に付けた技を、商店街公式フリマアプリ「fleama」開発室の仕事で試す演習専用コースです。解説はありません。依頼と完成条件だけを読み、白紙から自分で組み立てます。手が止まったときのために、方針・使う構文・部分解の3段階のヒントを各問に用意しています。判定はコンパイラそのものです。型が通らないコードは実行される前に不合格になるので、まず tsc を黙らせてから中身を合わせることになります。基本型の注釈から始まり、関数の署名、any の撲滅、interface の設計、リテラルunion、型ガード、ジェネリクス、入口での変換、入れ子のAPIレスポンスを経て、最後は型のないモジュールを strict で通る形に組み直すところまで、10問で TypeScript の主要な道具を一巡します。1問10分から45分、全10問で約4.5時間です。TypeScript入門を終えて「読めるけれど自分では書けない」と感じている方に向いています。

1
初段
01. 赤い波線を消せ10分
02. 関数に正しい型を付けよ15分
03. このanyを撲滅せよ15分
04. Item型を設計せよ20分
2
中段
01. 不正な状態を型で封じよ25分
02. 型ガードで絞り込め25分
03. 汎用関数に型を付けよ30分
04. 型を付けて事故を防げ30分
3
師範
01. APIレスポンスに型を通せ35分
02. 総合 型のないモジュールを完全TS化45分

汎用関数に型を付けよ

中段 / 目安 30分

凛さんがリポジトリの検索結果を見せました。同じ形の関数が3つ並んでいます。firstItem、firstUser、firstNotice。中身は全部 arr[0] を返しているだけで、違うのは型注釈だけでした。

「一覧の先頭を取りたいのは、商品だけではありません。ユーザー一覧でも通知一覧でも同じことをします。中身が同じなのに3つあるのは、3箇所に直す義務があるということです」

では1つにまとめるとして、引数の型は何でしょうか。any[] にすれば全部通ります。しかし通った先で、戻り値も any になります。first(users)[0].nmae と綴りを間違えても、コンパイラは何も言いません。凛さんの一言は短いものでした。「anyは論外です」

ここで使うのが ジェネリクス です。関数名のうしろに <T> と書くと、T は「呼ばれるときに決まる型」を表す入れ物になります。first<T>(arr: T[]): T | undefined と書けば、商品の配列を渡したときは T が Item になり、戻り値は Item | undefined になります。文字列の配列を渡せば string | undefined です。呼び出しごとに型が付け替わるわけです。

呼ぶ側で <Item> と明示する必要はありません。渡した配列から TypeScript が推論します。ここが any との決定的な差です。any は型を捨てますが、ジェネリクスは型を 預かって、そのまま返します。

戻り値に | undefined が付いているのは、空の配列を渡されることがあるからです。前の問で扱った絞り込みが、そのまま呼び出し側の責任になります。凛さん曰く「汎用にするというのは、誰が使っても壊れないようにするということです。空配列は必ず来ます」

完成条件

次の2つを定義してください。

  1. first<T>(arr: T[]): T | undefined — 配列の先頭を返す。空なら undefined を返す
  2. summarize(items, users) — first を 2つの違う型で呼んで、結果をまとめた文字列を返す

items は商品の配列です。商品は name (string) と price (number) を持ちます。users は名前の配列で、こちらは string[] です。

summarize が返す文字列は次の形です。

プレーンテキスト

商品 深煎りブレンド 480円 / 会員 田中はじめ

それぞれ先頭が無いときの表記は次のとおりです。

  • 商品が空のとき — 商品 なし
  • 会員が空のとき — 会員 なし

両方空なら 商品 なし / 会員 なし になります。区切りは半角スペースを挟んだ / です。

any は使わないでください。first の中で arr[0] をそのまま返すと、strict でも T として扱われてしまう点に注意してください。長さを確かめてから返すか、T | undefined を明示した受け取り方をしてください。どちらでも構いませんが、空配列で undefined が返ることを必ず確かめてください。

進め方

白紙から書いてください。手が止まったらヒントを開けます。ヒントは3段階で、①方針、②使う構文、③部分解の順に出ます。1つも開けずに通せたら、その問題は自分のものです。

開かずに10分粘って進まないなら、①だけ開けてください。粘る時間そのものには価値がありません。価値があるのは、自分で書いた行が動いた瞬間です。

要件

  1. first(arr: T[]): T | undefined を定義する。型引数を使い、any は使わない
  2. 空の配列を渡したら undefined が返ること
  3. interface Item を定義する。持つのは name(string) と price(number)
  4. summarize(items: Item[], users: string[]): string を定義する
  5. summarize は first を Item[] と string[] の2つの型で呼ぶこと
  6. 戻り値は「商品 深煎りブレンド 480円 / 会員 田中はじめ」の形
  7. 先頭が取れなかった側は「商品 なし」「会員 なし」と書く

入出力例

summarize([{"name":"深煎りブレンド","price":480},{"name":"抹茶ラテ","price":580}], ["田中はじめ","佐藤みどり"]) → "商品 深煎りブレンド 480円 / 会員 田中はじめ" summarize([], ["佐藤みどり"]) → "商品 なし / 会員 佐藤みどり" summarize([{"name":"チーズケーキ","price":480}], []) → "商品 チーズケーキ 480円 / 会員 なし" summarize([], []) → "商品 なし / 会員 なし" summarize([{"name":"季節のフルーツティー","price":620},{"name":"自家製レモネード","price":560},{"name":"カフェオレ","price":520}], ["山口あゆみ","中村けいた","小林ひかる"]) → "商品 季節のフルーツティー 620円 / 会員 山口あゆみ"

ヒント

前のヒントを開くと次が開きます。開かずに解けると未開封クリアです

①方針 まず first だけを作って、数値の配列と文字列の配列の両方で呼んでみます。呼び出し側にマウスを置くと、戻り値の型が number | undefined と string | undefined に化けるはずです。それが確認できたら summarize に進みます

ヒント 2ヒント 1 を開くと読めます
ヒント 3ヒント 2 を開くと読めます
生田 陸人
監修生田 陸人
ゆめさくエンジニア / 現役ソフトウェアエンジニア監修者プロフィールを見る →
編集 ゆめさく編集部·公開 2026/08/09

関連レッスン

  • 型を付けて事故を防げ

    嘘の型が付いたデータに正直な型を付け直すと、掛け算の行が赤くなります。入口で直してから通します。

  • APIレスポンスに型を通せ

    入れ子になったAPIレスポンスを、as を使わずに型の入れ子で表します。通信の向こう側まで型を届かせます。

  • 型ガードで絞り込め

    Item | undefined を早期 return で絞り込みます。ガードを消すと本当に赤くなることも自分の手で確かめます。

  • 不正な状態を型で封じよ

    商品状態を3つのリテラルの union にし、それ以外が存在できない世界にします。typo が実行前に死にます。

このレッスンに出てくる用語

意味があいまいなまま進んだ語は、ここから読み直せます。

  • リポジトリプロジェクトの保管場所
  • 関数処理に名前を付けて再利用できる単位
  • 型注釈変数や引数に : 型 で宣言する書式
  • 引数位置引数=順番で渡す。
  • 戻り値呼び出し元への返答を表す点線矢印
  • ジェネリクス<String> のように型を指定する仕組み
  • 配列サイズ固定の同型データの集まり
  • TypeScript静的型を加えた JavaScript の上位互換
main.ts
学習モード
エディタを読み込んでいます

メモ

汎用関数に型を付けよ

⌘S で保存