TypeScriptの型を復習しましょう

この記事は、 PLEX Advent Calendar 2024の5日目の記事です。

今回紹介する下記の型は、
よく使うもの、見かけた時にすぐに理解しづらいものを選んでいます。

TypeScriptの型を復習しましょう
TypeScriptの型を復習しましょう

リテラル

特定の値そのものを型として使用できる機能

// 例1:
type Direction = "north" | "south" | "east" | "west"
let myDirection: Direction = "north" // OK
myDirection = "up"                   // ❌ エラー: "up"は許可されていない

// 例2:
// HTTP メソッドを制限する
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE"

function request(url: string, method: HttpMethod) {
  // ...
}

request("/api/users", "GET")    // OK
request("/api/users", "PATCH")  // ❌ エラー

ユニオン型( | )

複数の型を組み合わせて、型の選択肢を作る機能

// 1. 基本的なユニオン型
type StringOrNumber = string | number
let value: StringOrNumber
value = "hello"  // OK
value = 42       // OK
value = true     // ❌ エラー: booleanは許可されていない

型の交差( & )

全ての型の特徴を持つ新しい型を作る機能

type BasicInfo = {
  id: number    // 数値型のID
  name: string  // 文字列型の名前
}
type PersonalUser = BasicInfo & {
  type: "personal"  // リテラル型で "personal" という固定値
  age: number       // 数値型の年齢
}

使用例:
const user: PersonalUser = {
  id: 1,
  name: "田中",
  type: "personal",
  age: 25
}

マップ型

既存の型を基に、新しい型を作成する機能

type GameSearchState = {
  gameId: number
  nameQuery: string
  isSoldOut: boolean
}
type UrlTypes<T> = {
  [K in keyof T as KebabCase<K>]: string
}
  1. ジェネリック型(<T>)を使用
  2. マップ型([K in keyof T])で型のキーをイテレート
  3. as KebabCase<K>でキーをケバブケース(ハイフン区切り)に変換
  4. 全ての値の型をstringに設定
// UrlTypes<GameSearchState>
{
  "game-id": string,
  "name-query": string,
  "is-sold-out": string
}

条件付き型 (T extends U ? X : Y)

条件に応じた型の分岐

[K in keyof T as T[K] extends number ? K : never]: T[K]
  1. K in keyof T - 型Tのすべてのキーをイテレート
  2. as T[K] extends number ? K : never - 条件付き型で、値の型に基づいてキーをフィルタリング
  3. : T[K] - マッピングされた各キーの値の型を元の型と同じに この型は「数値型のプロパティのみを抽出する」という動作をする
type Example = {
  id: number
  name: string
  age: number
  isActive: boolean
}

type NumberPropertiesOnly = {
  [K in keyof Example as Example[K] extends number ? K : never]: Example[K]
}

// 結果として生成される型:
// {
//   id: number
//   age: number
// }

ReturnType

組み込みユーティリティ型の1つ
関数の戻り値の型を抽出する際に使用される

// 1. 基本的な関数の例
function getMessage(): string {
  return "Hello"
}

type MessageType = ReturnType<typeof getMessage>
// MessageType は string

// 2. 複雑な戻り値を持つ関数
function fetchUser() {
  return {
    id: 1,
    name: "田中",
    age: 25
  }
}

type User = ReturnType<typeof fetchUser>
// User は {
//   id: number
//   name: string
//   age: number
// }

Parameters

組み込みユーティリティ型の1つ
関数の引数の型をタプル型として抽出

// 1. 基本的な関数の例
function greet(name: string, age: number) {
  return `Hello, ${name}! You are ${age} years old.`
}

type GreetParams = Parameters<typeof greet>
// GreetParams は [string, number]

// パラメータを個別に取得することも可能
type FirstParam = Parameters<typeof greet>[0]  // string
type SecondParam = Parameters<typeof greet>[1] // number

// 2. オブジェクトパラメータを持つ関数
function createUser(params: {
  name: string
  age: number
  email: string
}) {
  return params
}

type CreateUserParams = Parameters<typeof createUser>[0]
// CreateUserParams は {
//   name: string
//   age: number
//   email: string
// }

as const, satisfies

const localizeData = {
  novel: {
    chapter: "チャプター",
    episode: "話",
  },
} as const satisfies {
  novel: {
    chapter: string
    episode: string
  }
}

as constの役割:
1. 全てのプロパティを読み取り専用(readonly)にする
2. リテラル型として扱われるようになる
3. "チャプター"string型ではなく"チャプター"というリテラル
satisfiesの役割:
1. オブジェクトが特定の型を満たすことを検証
2. 型の検証と推論の両方の利点を得られる
3. エラーを早期に発見できる

// ✅ OK - 型チェックが通る
const chapter = localizeData.novel.chapter  
// 型は "チャプター" (リテラル型)

// ❌ エラー - 読み取り専用なので変更不可
localizeData.novel.chapter = "章"  

// ✅ OK - 型チェックによる補完が効く
const hasChapter = "chapter" in localizeData.novel

この構文の利点:

  1. 型安全性:
    • オブジェクトの構造が指定した型と一致することを保証
    • 必要なプロパティの欠落を防ぐ
  2. リテラル型の保持:
    • as constにより、具体的な文字列値が型として保持される
    • より厳密な型チェックが可能
  3. IDE支援:
  4. 実行時の不変性:
    • オブジェクトとその中身が読み取り専用になる

参考元:
値・型・変数 | TypeScript入門『サバイバルTypeScript』
TypeScript特有の組み込み型関数 - log.pocka.io