Generics

ジェネリクス

Generics を分かりやすく

「Generics」という語は、一見すると頭が痛くなりそうですよね。しかし、実際には、とても便利な機能で、TypeScriptでの開発を非常に強力にサポートします。

Genericsは、型の再利用性を高めるための強力なツールです。型が一部未定の汎用的な関数やクラスを作成し、それを具体的な型で再利用することができます。これにより、安全性と再利用性を保ちつつ、さまざまな型に対応するコードを作成できます。

例え話で考えてみましょう。例えば、あなたがスーパーマーケットで「保存容器」を買いに行ったとします。保存容器は色んなものを保存するための「汎用的なツール」ですよね。野菜でも肉でも、お菓子でも、何でも保存できます。

この保存容器がまさにGenericsです。何を入れるかは未定ですが、何かしらを保存するための容器(つまり、機能)があります。その後、家に帰って実際に何を保存するか決めるまで待ちます。あなたがトマトを保存することを決めたら、その保存容器は「トマトを保存する容器」となります。同じように、ジェネリック関数やクラスも、具体的な型が与えられるまで何を処理するかは未定です。

Genericsをうまく使うと、TypeScriptのコードがより柔軟で再利用可能になり、同時に型安全性も保てます。これって、すごくないですか?一体ナンノコッチャ?って思うかもしれませんが、こうやって理解すれば難しくないですよね?ここまで大丈夫ですか?

まとめます。Generics(ジェネリックス)は、TypeScript で提供される機能で、型の汎用性と再利用性を向上させるためのものです。Generics を使用することで、関数やクラス、インターフェースに対して、型をパラメータとして渡すことができます。これにより、コードの繰り返しを減らし、一貫性を保つことができます。

Generics を使うメリット

Generics のメリットは主に以下の 3 つです。

  1. 型の汎用性: 一つの関数やクラスで複数の型を扱えるようになり、型ごとに関数やクラスを作成する必要がなくなります。
  2. 型の安全性: 型情報がコンパイル時にチェックされるため、実行時のエラーを防ぐことができます。
  3. コードの再利用性: 同じロジックを持つコードを複数回書く必要がなくなり、保守性が向上します。

Generics を実装

では、Next.js と TypeScript を用いて Generics を活用した実装を見ていきましょう。ここでは、状態を管理するカスタムフックを作成します。このフックでは、状態とその更新関数を返します。

まず、Generics を使用しない場合の実装を見てみましょう。

import { useState } from 'react'

type UseCustomStateResult<T> = [T, (value: T) => void]

const useCustomState = (initialState: string): UseCustomStateResult<string> => {
  const [state, setState] = useState(initialState)

  return [state, setState]
}

この実装では、useCustomState は文字列の状態を管理するために使用されますが、他の型に対応していません。ここで Generics を導入して、このカスタムフックを改善してみましょう。

import { useState } from 'react'

type UseCustomStateResult<T> = [T, (value: T) => void]

function useCustomState<T>(initialState: T): UseCustomStateResult<T> {
  const [state, setState] = useState(initialState)

  return [state, setState]
}

Generics を使用することで、useCustomState が任意の型を受け入れることができるようになりました。このカスタムフックは、文字列だけでなく、数値やオブジェクト、配列など、さまざまな型の状態を管理するために使用できます。以下に具体的な使用例を示します。

// 文字列型の状態管理
const [text, setText] = useCustomState<string>('Initial text')

// 数値型の状態管理
const [count, setCount] = useCustomState<number>(0)

// オブジェクト型の状態管理
type User = {
  id: number
  name: string
}

const initialUser: User = {
  id: 1,
  name: 'John Doe',
}

const [user, setUser] = useCustomState<User>(initialUser)

// 配列型の状態管理
const [items, setItems] = useCustomState<number[]>([])

上記の例では、useCustomState が Generics を活用して、さまざまな型の状態管理に対応しています。これにより、コードの汎用性が向上し、再利用性も高まります。

Generics を活用したコンポーネントの実装

Generics を使用して、Next.js と TypeScript で汎用的なリストコンポーネントを作成してみましょう。このコンポーネントは、さまざまな型のリストアイテムをレンダリングすることができます。

まず、リストコンポーネントのプロップス型を定義します。

type ListProps<T> = {
  items: T[]
  renderItem: (item: T) => React.ReactNode
}

次に、リストコンポーネントを実装します。

const List = <T>({ items, renderItem }: ListProps<T>): JSX.Element => {
  return (
    <ul>
      {items.map((item, index) => (
        <li key={index}>{renderItem(item)}</li>
      ))}
    </ul>
  )
}

このリストコンポーネントは、任意の型のアイテムを受け取り、renderItem 関数を使用してアイテムをレンダリングします。以下に具体的な使用例を示します。

const users: User[] = [
  { id: 1, name: 'John Doe' },
  { id: 2, name: 'Jane Doe' },
]

const UserList = (): JSX.Element => {
  return (
    <List<User>
      items={users}
      renderItem={(user) => (
        <div>
          <div>ID: {user.id}</div>
          <div>Name: {user.name}</div>
        </div>
      )}
    />
  )
}

この例では、User 型のオブジェクトのリストが、Generics を活用したリストコンポーネントに渡されています。リストコンポーネントは、renderItem 関数を使用して、適切な形式でアイテムをレンダリングします。このリストコンポーネントは、User 型だけでなく、任意の型のリストアイテムに対応することができます。

例えば、数値のリストもレンダリングできます。

const numbers: number[] = [1, 2, 3, 4, 5]

const NumberList = (): JSX.Element => {
  return <List<number> items={numbers} renderItem={(number) => <div>Number: {number}</div>} />
}

このように、Generics を使用することで、コンポーネントの汎用性が向上し、さまざまな型のデータを扱うことができるようになります。

まとめ

Generics は、TypeScript の強力な機能で、型の汎用性と再利用性を向上させることができます。Next.js と TypeScript を組み合わせて、Generics を活用することで、型安全性を保ちながら、コンポーネントやカスタムフックのコードを再利用できるようになります。これにより、効率的で保守性の高いアプリケーションを開発することができます。