4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

気まぐれな「猫」の行動で理解する TypeScript の型 🐈

Last updated at Posted at 2024-11-09

こんにちは、とまだです。

TypeScript アドベントカレンダー 2024 のうち、1 日目の記事をお届けします!

突然ですが、みなさんは猫は好きですか?

私は猫を飼っていて、毎日のように猫に癒されています。

今回は、そんな猫の 1 日を通じて、TypeScript の型システムを理解していきましょう 🐱

1. インターフェース:朝ごはんを待つタマ

今回は「タマ」という名前の猫を例にしてみましょう。

朝 7 時。タマが起き出して、朝ごはんをねだってきました。

「にゃ〜」(ごはん!)
「にゃーん」(早くー!)
「にゃ?」(なんで起きないの?)

このように、タマには決まった情報その時々で変わる情報があります。

TypeScript では、このような「あるものが持つべき情報の形」を interface(インターフェース) で表現します。

interface Cat {
  // 決まった情報
  name: string; // 名前
  age: number; // 年齢
  color: string; // 毛色

  // 変わる情報
  isHungry: boolean; // お腹が空いているか
  energy: number; // 元気度(0-100)
  lastMeal: Date; // 最後に食べた時間
}

// タマの今の状態
const momo: Cat = {
  name: "タマ",
  age: 3,
  color: "茶トラ",
  isHungry: true,
  energy: 80,
  lastMeal: new Date("2024-02-20T20:00:00"),
};

こうしておくと、以下のようなメリットがあります。

  • タマの状態を正確に記録できる
  • 必要な情報が抜けていないか確認できる
  • 間違った型の情報(例:年齢を文字列で入れるなど)を防げる

たとえば、momo.age = "3歳" とすると、TypeScript がエラーを出してくれます。
余計な情報を入れないように、タマの情報を管理できるのです。

2. Union Types:気まぐれなタマの機嫌

朝ごはんを食べ終えたタマ。さて、この後どうするでしょう?

猫って本当に気まぐれですよね。

  • 「遊んで!」とおもちゃを持ってくる
  • 「zzz...」と急に寝始める
  • 「構って!」と膝の上に乗ってくる
  • 「そっとしておいて」と距離を取る

このように、決まった選択肢の中から 1 つを選ぶような状況を、TypeScript では Union Types(合併型) で表現します。

// タマの機嫌を表すUnion Type
type CatMood = "遊びたい" | "寝たい" | "構ってほしい" | "そっとしておいて";

// タマの状態にmoodを追加
interface CatWithMood extends Cat {
  currentMood: CatMood;
}

const momo: CatWithMood = {
  name: "タマ",
  age: 3,
  color: "茶トラ",
  isHungry: false,
  energy: 80,
  lastMeal: new Date(),
  currentMood: "遊びたい", // この4つの選択肢以外は指定できない!
};

// タマの機嫌に応じた対応をする関数
function handleCatMood(mood: CatMood): string {
  switch (mood) {
    case "遊びたい":
      return "ねこじゃらしで遊んであげよう";
    case "寝たい":
      return "そっとしておこう";
    case "構ってほしい":
      return "膝の上でなでさせてもらおう";
    case "そっとしておいて":
      return "距離を置こう";
  }
}

Union Types を使うと、このようにいくつかのパターンを列挙して、それ以外の値を受け付けないようにできます。

momo.currentMood = "おかきを食べたい"; // 選択肢にないのでエラー

実務でも、このように「決まった選択肢」を型として定義することで、間違った値を防ぐことができます。

3. Optional Properties:予測不能なタマの行動

お昼頃、タマが突然走り回り始めました。
そういえば昨日は「カリカリ」を床にばらまいて遊んでいたような...?

このように、猫は予期せぬ行動を見せることがありますよね。
「今日だけの特別な行動」や「たまにしかしない行動」というものです。

TypeScript では、このような「あるかもしれないし、ないかもしれない」性質を Optional Properties で表現できます。

interface CatBehavior {
  // 基本的な行動(必ず記録)
  location: string; // 居場所
  action: string; // している事

  // 特別な行動(あれば記録)
  specialAction?: string; // その日だけの特別な行動
}

// いつもの日のタマ
const normalDay: CatBehavior = {
  location: "リビング",
  action: "寝る",
};

ここまではいつもの日のタマですが、Optional Properties を使うと、予測不能な情報を柔軟に扱えます。

// 予測不能な行動を記録
const unexpectedBehavior: CatBehavior = {
  location: "リビング",
  action: "走り回る",
  specialAction: "カリカリを床にばらまく",
};

実務でも、このように「必須の情報」と「任意の情報」を分けて記録することで、柔軟に情報を管理できます。

4. Generic Types:タマのおもちゃ箱作戦

夕方になり、リビングに散らかったタマのおもちゃを整理することにしました。

  • ボールのおもちゃ箱
  • ぬいぐるみの箱
  • 猫じゃらしの箱

この「箱に何かを入れる」という考え方も、TypeScript で表現できます。

それが Generic Types(総称型) です。

// おもちゃの基本情報
interface Toy {
  name: string; // おもちゃの名前
  isBroken: boolean; // 壊れているか
  boughtDate: Date; // 買った日
}

// 箱の定義(中に入れるものの型は後で決める)
interface Box<T> {
  items: T[]; // 箱の中身
  addItem(item: T): void; // アイテムを追加
  getFavorite(): T; // お気に入りを取得
}

// ボールの定義
interface Ball extends Toy {
  type: "転がる" | "光る" | "鈴付き";
}

// ぬいぐるみの定義
interface Plushie extends Toy {
  animal: string;
  hasCartnip: boolean; // またたび入り
}

// 猫じゃらしの定義
interface Teaser extends Toy {
  material: "" | "" | "";
  length: number; // センチメートル
}

// それぞれの箱を作る
// ボールの箱
const ballBox: Box<Ball> = {
  items: [
    {
      name: "光るボール",
      type: "光る",
      isBroken: false,
      boughtDate: new Date("2024-01-15"),
    },
  ],
  addItem(ball) {
    this.items.push(ball);
  },
  getFavorite() {
    return this.items[0];
  },
};

// ぬいぐるみの箱
const plushieBox: Box<Plushie> = {
  items: [
    {
      name: "ネズミさん",
      animal: "ねずみ",
      hasCartnip: true,
      isBroken: false,
      boughtDate: new Date("2023-12-25"),
    },
  ],
  addItem(plushie) {
    this.items.push(plushie);
  },
  getFavorite() {
    return this.items[0];
  },
};

// 猫じゃらしの箱
const teaserBox: Box<Teaser> = {
  items: [
    {
      name: "きらきら羽じゃらし",
      material: "",
      length: 45,
      isBroken: false,
      boughtDate: new Date("2024-02-01"),
    },
  ],
  addItem(teaser) {
    this.items.push(teaser);
  },
  getFavorite() {
    return this.items[0];
  },
};

ちょっと複雑に見えますが、Generic Types を使うことで、同じ構造で異なる型を扱うことができます。

// 新しいボールを追加
ballBox.addItem({
  name: "鈴付きボール",
  type: "鈴付き",
  isBroken: false,
  boughtDate: new Date(),
});

// それぞれの箱からお気に入りを取得
const favoriteBall = ballBox.getFavorite(); // Ball型
const favoritePlush = plushieBox.getFavorite(); // Plushie型
const favoriteTeaser = teaserBox.getFavorite(); // Teaser型

アプリが大きくなっても、型の整合性を保ちながら柔軟に拡張できるので、実務でもよく使われる手法です。

5. Mapped Types:タマの一日を記録しよう

夜になり、今日のタマの様子を日記につけることにしました。
「朝」「昼」「夕方」「夜」、それぞれの時間でタマは何をしていたでしょう?

このように「決まった時間帯それぞれに情報を記録したい」という場合、Mapped Types(写像型) が便利です。

// 時間帯を定義
type TimeOfDay = "" | "" | "夕方" | "";

// 各時間帯の記録内容
interface DailyActivity {
  location: string; // どこにいた?
  mood: CatMood; // 機嫌は?
  memo?: string; // 特記事項
}

// 一日の記録を作る
type DailySchedule = {
  [Time in TimeOfDay]: DailyActivity; // 時間帯ごとの記録
};

// タマの一日
const momosDay: DailySchedule = {
  : {
    location: "キッチン",
    mood: "お腹すいた",
    memo: "いつもより30分早く起きた",
  },
  : {
    location: "窓際",
    mood: "そっとしておいて",
  },
  夕方: {
    location: "リビング",
    mood: "遊びたい",
    memo: "新しいおもちゃで遊ぶ",
  },
  : {
    location: "ソファー",
    mood: "寝たい",
  },
};

Mapped Types を使うと、このように「構造化された情報」を管理することができます。

momosDay. = { location: "ベッド", mood: "寝る" }; // OK
momosDay. = { location: "ベッド", mood: "" }; // エラー
```

## まとめ:タマと学んだ TypeScript の型システム

タマの一日を通じて、TypeScript の主要な型システムについて学んできました。

最後に軽く振り返ってみましょう。

1. **Interface(インターフェース)**

   - タマの基本情報を定義
   - 必要な情報を明確に示せる

2. **Union Types(合併型)**

   - タマの気分のように、決まった選択肢を表現
   - 想定外の値を防げる

3. **Optional Properties(省略可能なプロパティ)**

   - 予測不能な猫の行動をうまく記録
   - 必要なときだけ情報を追加できる

4. **Generic Types(総称型)**

   - おもちゃ箱のような「入れ物と中身」の関係を表現
   - 同じ構造で異なる型を扱える

5. **Mapped Types(写像型)**
   - 一日の記録のように、構造化された情報を管理
   - 必要な情報の漏れを防げる

他にもアドベントカレンダー記事を書いています!

他にも、2024 年のアドベントカレンダーに参加しています。

以下の記事でまとめているので、よければ他の記事も読んでいただけると嬉しいです!

4
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?