TL;DR
この記事では、TypeScript の重要な概念であるジェネリック型を Next.js の開発環境でどのように使うかについて解説します。ジェネリック型の基本的な理解から、具体的な Next.js での実装方法まで、手を動かしながら学べる内容になっています。
| 開発環境 | バージョン | 
|---|---|
| Next.js | 13.4.3 | 
| TypeScript | 5.0.4 | 
| Emotion | 11.11.0 | 
| React | 18.2.0 | 
ジェネリック型とは?
ジェネリクスは、まるで「箱」のようなものだと考えてみてください。この箱は特別で、何を入れてもその形状に変わります。たとえば、リンゴを入れるとリンゴの形に、サッカーボールを入れるとボールの形に、おもちゃの恐竜を入れると恐竜の形に変わる不思議な箱です。
TypeScript のジェネリック型も同じように、何かを「入れる」ことでその型になる機能を持っています。これにより、同じ関数やクラスで様々な型を扱うことができます。
次の例を見てみましょう。
function returnSame<T>(arg: T): T {
  return arg
}
この returnSame という関数は、ジェネリック型 T を引数 arg に使っています。この T がまさに前述の「不思議な箱」です。これを使うと、どんな型でも引数に入れて、そのまま返すことができます。
例えば次のように使用できます。
let outputString = returnSame<string>('hello') // outputString は 'hello'
let outputNumber = returnSame<number>(100) // outputNumber は 100
ここでは T の部分に具体的な型を入れて呼び出しています。すると、その関数はその型に合わせて動作します。つまり、不思議な箱にリンゴを入れたらリンゴの形に、サッカーボールを入れたらボールの形になるように、ジェネリクスもまた、指定した型に合わせて動作します。
このように、ジェネリック型は様々な型を柔軟に扱うための、とても強力なツールなんですね。
さらにわかりやすい例をご紹介しましょう。
ジェネリック型を使ったシンプルな関数を考えてみましょう。それが「箱」を作る関数だと想像してみてください。この「箱」はとても特殊で、何でも入れることができ、箱の中身はそのまま保管されます。
function createBox<T>(item: T) {
  return { content: item }
}
上記の createBox 関数は、ジェネリック型 T を使用しています。この関数に何かを渡すと、それがそのまま箱の中身 content となります。そして、この content の型は入れたもの item の型になります。そのため、この関数はどんな型でも扱うことができます。
それではこの関数を使ってみましょう。
let boxOfNumber = createBox(10) // { content: 10 }
let boxOfString = createBox('Hello') // { content: "Hello" }
createBox に数値を渡すと、その数値を保管した箱が返されます。同様に、文字列を渡すと、その文字列を保管した箱が返されます。
このようにジェネリクスは、とてもフレキシブルで、あらゆる種類の「箱」を作ることができます。だから、それがなんでも受け入れる「不思議な箱」のようなものだと言われるんですね。
アロー関数を使用した場合も、ジェネリック型の使用方法は同様です。以下にアロー関数を使った例をご紹介します。
まずは先程の「箱を作る」関数をアロー関数に書き換えてみましょう。
const createBox = <T,>(item: T) => {
  return { content: item }
}
この createBox 関数はジェネリック型 T を使っています。引数 item の型は T となり、そのまま content として保管されます。
それではこのアロー関数を使ってみましょう。
let boxOfNumber = createBox(10) // { content: 10 }
let boxOfString = createBox('Hello') // { content: "Hello" }
createBox に数値を渡すと、その数値を保管した箱が返されます。同様に、文字列を渡すと、その文字列を保管した箱が返されます。
関数式でも、アロー関数でも、ジェネリック型の使い方は基本的に同じです。ただし、宣言的な関数とアロー関数では、this の挙動が異なることに注意が必要です。どちらを使用するかは、その関数がどのように使用されるかによります。
まとめると、ジェネリック型は、TypeScript の機能で、再利用可能なコードを作成するための強力なツールといえます。これは、型の一部をパラメータとして扱うことで、コードの再利用性を高め、型の安全性を保つことができます。
function identity<T>(arg: T): T {
  return arg
}
この関数は、どんな型の引数でも受け取ることができ、そのまま返すことができます。このように、ジェネリック型は型の一部をパラメータ化して、一般化された関数やクラスを作成することができます。
Next.js で、ジェネリック型を活用
それでは、Next.js の開発において、ジェネリック型をどのように活用できるか見ていきましょう。ここでは、フェッチしたデータを扱うためのカスタムフックを作成します。
// hooks/useFetch.ts
import { useState, useEffect } from 'react'
type UseFetch<T> = [T | null, boolean, Error | null]
function useFetch<T>(url: string): UseFetch<T> {
  const [data, setData] = useState<T | null>(null)
  const [loading, setLoading] = useState(false)
  const [error, setError] = useState<Error | null>(null)
  useEffect(() => {
    const fetchData = async () => {
      setLoading(true)
      try {
        const response = await fetch(url)
        const data = await response.json()
        setData(data)
      } catch (error) {
        setError(error)
      } finally {
        setLoading(false)
      }
    }
    fetchData()
  }, [url])
  return [data, loading, error]
}
export default useFetch
このカスタムフックは、任意の型 T のデータをフェッチし、そのデータとローディング状態、エラーを配列として返すものです。ここで T はジェネリック型で、使用時に具体的な型を指定することで、その型のデータを扱うことができます。
ジェネリック型を活用
以上で示したカスタムフックを、実際のコンポーネントで使ってみましょう。今回は、次のような型のデータを取得する API を想定します。
type UserData = {
  id: string
  name: string
  email: string
}
そして、この型のデータを取得するコンポーネントを以下のように作成します。
// components/User.tsx
import useFetch from '../hooks/useFetch'
type UserData = {
  id: string
  name: string
  email: string
}
const User = () => {
  const [user, loading, error] = useFetch<UserData>('/api/user')
  if (loading) {
    return <div>Loading...</div>
  }
  if (error) {
    return <div>Error: {error.message}</div>
  }
  return (
    <div>
      <h1>{user?.name}</h1>
      <p>{user?.email}</p>
    </div>
  )
}
export default User
ここで useFetch<UserData> としてジェネリック型を指定することで、user の型が UserData になります。これにより、user のプロパティに安全にアクセスすることができます。
Emotion で実装
それでは最後に、Emotion を使ってスタイルを適用してみましょう。Emotion もジェネリック型を活用することで、コンポーネントのプロパティに型安全性を提供します。
// components/StyledUser.tsx
import { css } from '@emotion/react'
import useFetch from '../hooks/useFetch'
type UserData = {
  id: string
  name: string
  email: string
}
type Props = {
  color: string
}
const StyledUser = ({ color }: Props) => {
  const [user, loading, error] = useFetch<UserData>('/api/user')
  if (loading) {
    return <div>Loading...</div>
  }
  if (error) {
    return <div>Error: {error.message}</div>
  }
  return (
    <div
      css={css`
        color: ${color};
      `}
    >
      <h1>{user?.name}</h1>
      <p>{user?.email}</p>
    </div>
  )
}
export default StyledUser
ここで、Props 型の color プロパティを受け取り、それを Emotion の css 関数で使用しています。この css 関数は、テンプレートリテラル内でジェネリクスを活用することで、スタイルプロパティに型安全性を提供します。