Next.js と TypeScript で使う、インデックスシグネチャを理解する

TwitterFacebookHatena

TL;DR

このページでは、インデックスシグネチャの実装方法について解説しますね。一言でいうと、インデックスシグネチャとはオブジェクトのプロパティに対する型付けの一種です。具体的なコードとともに見ていきましょう。

開発環境は以下のとおりです。

開発環境 バージョン
Next.js 13.4.4
TypeScript 5.0.4
Emotion 11.11.0
React 18.2.0

インデックスシグネチャとは?

TypeScript では、オブジェクトのプロパティに対して、特定の型を割り当てることができます。しかし、プロパティ名が固定ではなく動的である場合や、予めプロパティ名がわからない場合はどうでしょうか?ここでインデックスシグネチャが役立ちます。

インデックスシグネチャは、オブジェクトのプロパティに対する動的な型付けを可能にします。 文字列か数値でアクセスできるプロパティに対して型付けを提供します。

基本的なソースコードは次のようになります。

type IndexSignatureExample = {
  [key: string]: number
}

const example: IndexSignatureExample = {
  property1: 1,
  property2: 2,
  property3: 3,
}

ここで、[key: string]: numberがインデックスシグネチャの部分です。keyは任意の名前をつけることができ、この例では文字列型のプロパティに対して数値型の値を割り当てています。これにより、exampleオブジェクトは、どのような文字列のプロパティでも受け入れ、その値は必ず数値型となります。もし数値以外の値を設定しようとすると、TypeScript は型エラーを出します。

インデックスシグネチャは、オブジェクトのプロパティ名とそれに対応する値の型を動的に制御する強力なツールです。以下に、様々なシナリオでのインデックスシグネチャの使用例を挙げてみます。

異なる型の値を持つプロパティ

例えば、stringnumber の値を持つことができるオブジェクトを表現するには、次のような型を作成します。

type MyObject = {
  [key: string]: string | number
}

これにより、キーが文字列で、値が文字列または数値であるオブジェクトを表現することができます。

let obj: MyObject = {}

obj['a'] = 'hello' // OK
obj['b'] = 123 // OK
obj['c'] = true // Error: boolean is not assignable to string | number

これにより、インデックスシグネチャで異なる型の値を持つことができます。ただし、この方法では全てのキーが同じ型の値を持つことになるため、特定のキーが特定の型の値を持つ、といったより詳細な型制約を表現することはできません。特定のキーに特定の型の値を割り当てる必要がある場合には、インターフェースや型エイリアスを使ってそれぞれのキーとその型を明示的に指定すると良いでしょう。

ネストされたオブジェクト

プロパティの値が更にオブジェクトである場合にもインデックスシグネチャは使えます。ここでも、そのネストされたオブジェクトのプロパティの型を動的に制御できます。

type IndexSignatureExample = {
  [key: string]: {
    [key: string]: number
  }
}

const example: IndexSignatureExample = {
  object1: {
    property1: 1,
    property2: 2,
  },
  object2: {
    property1: 3,
    property2: 4,
  },
}

配列を値とするプロパティ

値が配列であるプロパティもインデックスシグネチャで制御できます。これにより、配列の要素の型も指定することが可能です。

type IndexSignatureExample = {
  [key: string]: string[]
}

const example: IndexSignatureExample = {
  property1: ['value1', 'value2'],
  property2: ['value3', 'value4'],
}

注意すべき点

インデックスシグネチャを使用する際にはいくつか注意点があります。

型安全性の損失

[key: string]: any のような汎用的なインデックスシグネチャを使用すると、型安全性が損なわれる可能性があります。具体的には、任意のキーに任意の値を割り当てることが可能になるため、予期しない型の値が割り当てられてしまうことがあります。そのため、特定のキーに特定の型の値のみを割り当てるべき場合などには、このようなインデックスシグネチャの使用は避けるべきです。

名前付きプロパティとの互換性

インデックスシグネチャの型は、同一オブジェクト内の名前付きプロパティの型と互換性がなければなりません。つまり、名前付きプロパティの型は、インデックスシグネチャの型の一部(または全体)であるべきです。

例えば、以下のような型はエラーになります。

type IndexSignatureExample = {
  [key: string]: number
  specificProperty: string // Error: string型はnumber型と互換性がない
}

インデックスシグネチャのキーの型

インデックスシグネチャのキーの型は、string または number でなければなりません。また、同一オブジェクト内では、string キーのインデックスシグネチャと number キーのインデックスシグネチャを同時に使用することはできません。

Next.js で、インデックスシグネチャを実装

それでは、Next.js プロジェクトにおいて、インデックスシグネチャをどのように実装するのか見ていきましょう。

ファイル名:pages/index.tsx

import React from 'react'
import { GetServerSideProps } from 'next'

type Props = {
  data: {
    [key: string]: string
  }
}

const Home = ({ data }: Props) => {
  return (
    <div>
      {Object.keys(data).map((key, index) => (
        <p key={index}>{`${key}: ${data[key]}`}</p>
      ))}
    </div>
  )
}

export const getServerSideProps: GetServerSideProps = async () => {
  const data = {
    message: 'Hello, Next.js!',
    datetime: new Date().toISOString(),
  }

  return {
    props: {
      data,
    },
  }
}

export default Home

1 行目と 2 行目では、React と Next.js の型をインポートしています。4 行目から 8 行目で Props 型を定義しています。この Props 型では、data プロパティがインデックスシグネチャを持つことを示しています。ここで、[key: string]: stringがインデックスシグネチャの部分で、文字列型のプロパティに対して文字列型の値を割り当てています。

10 行目から 19 行目では、Home コンポーネントを定義しています。このコンポーネントでは、受け取った data の各プロパティをリスト形式で表示します。16 行目で、Object.keys(data)を使用して data オブジェクトのすべてのキー(プロパティ名)を取得し、それぞれのキーに対して文字列を作成します。

21 行目から 30 行目では、getServerSideProps 関数を定義しています。この関数は Next.js のサーバーサイドレンダリングを制御します。ここでは、data オブジェクトを定義し、そのプロパティにメッセージと現在の日時を設定しています。そして、この data オブジェクトを Home コンポーネントの props として返しています。

Next.js と TypeScript で使う、インデックスシグネチャを理解する