Next.js と TypeScript で、タプル型を学ぶ

TwitterFacebookHatena

TL;DR

このページでは、タプル型の実装方法について詳しく解説しますね。一言でいうと、タプル型とは異なるデータ型をまとめて管理できる配列のようなものです。

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

「タプル型」とは?

タプル型とは、TypeScript で提供されている特殊な配列の型の一つで、異なる型の要素を一つの配列で管理することができます。通常の配列では、全ての要素が同じ型である必要がありますが、タプルではその限りではありません。これにより、配列の特定の位置に特定の型の値を持つことを保証することができます。

タプル型の基本的な定義方法は以下の通りです。

// タプル型の定義
let tuple: [string, number]
tuple = ['hello', 10] // OK
tuple = [10, 'hello'] // Error: 型が一致しないため

上記の例では、tupleという名前のタプルを定義し、それがstringnumberという二つの異なる型の要素を持つことを示しています。このとき、要素の順序も重要です。つまり、最初の要素はstring型でなければならず、次の要素はnumber型でなければならないということです。逆に並べると型の不一致エラーが発生します。

タプル型は数値、文字列、オブジェクトなど、異なる型の要素を組み合わせた配列を定義するのに役立ちます。さらに、Next.js と TypeScript を組み合わせると、これらの強力な機能を Web 開発に活用することができます。タプル型の他の使い方をいくつか見てみましょう。

要素のアクセスと代入

タプルは配列と同じようにアクセスできます。そして、一度定義されたタプルの型を満たす限り、再代入も可能です。

let tuple: [string, number]
tuple = ['hello', 10] // OK
console.log(tuple[0]) // 出力: "hello"
console.log(tuple[1]) // 出力: 10

tuple[0] = 'world' // OK
console.log(tuple[0]) // 出力: "world"

tuple[1] = 'foo' // Error: 'foo' の型 'string' は 'number' 型に代入できない

タプルの長さ

タプルの長さは、その定義によって固定されます。

let tuple: [string, number]
tuple = ['hello', 10] // OK

console.log(tuple.length) // 出力: 2

tuple = ['hello', 10, 20] // Error: 'string | number' 型の要素が必要ですが、'number' 型の要素が得られました

異なる型の要素

タプルは、要素ごとに異なる型を持つことができます。

let tuple: [string, number, boolean]
tuple = ['hello', 10, true] // OK

console.log(tuple[0]) // 出力: "hello"
console.log(tuple[1]) // 出力: 10
console.log(tuple[2]) // 出力: true

タプル内で配列を使用する

タプルは異なる型の要素を持つことができるため、配列も要素として持つことができます。

let tuple: [string, number, string[]]
tuple = ['hello', 10, ['a', 'b', 'c']] // OK

console.log(tuple[0]) // 出力: "hello"
console.log(tuple[1]) // 出力: 10
console.log(tuple[2]) // 出力: ["a", "b", "c"]

タプルの分割代入

タプルは分割代入を利用することもできます。これにより、コードを読みやすくできます。

let tuple: [string, number] = ['hello', 10]
let [text, number] = tuple // text = 'hello', number = 10

結果とエラーのハンドリング

関数が成功したときと失敗したときの結果を返すときにタプル型を使うことがあります。これは特に、非同期処理でよく見られます。以下の例は、Next.js の API ルートで使用できる、エラーハンドリングのためのヘルパー関数です。

// /utils/apiHelpers.ts
import { NextApiRequest, NextApiResponse } from 'next'

type Handler = (req: NextApiRequest, res: NextApiResponse) => Promise<void>

export const withErrorHandling = (handler: Handler): Handler => {
  return async (req, res) => {
    try {
      await handler(req, res)
    } catch (error) {
      console.error(error)
      res.status(500).json({ message: 'An unexpected error occurred.' })
    }
  }
}

この関数は、API ルートのハンドラを引数として受け取り、エラーをキャッチして 500 エラーレスポンスを送信する新しいハンドラを返します。これにより、API ルートの各ハンドラでエラー処理を個々に行う必要がなくなります。

複数の返り値

複数の値を返す関数は、一般的にはオブジェクトを返します。しかし、タプル型を使用すると、返り値の順序を保証することができます。例えば、次のような関数を考えてみてください。

// utils.ts
export function divide(a: number, b: number): [number, number] {
  if (b === 0) {
    throw new Error('Cannot divide by zero.')
  }
  const quotient = Math.floor(a / b)
  const remainder = a % b
  return [quotient, remainder]
}

この関数は商と余りを返すので、返り値は number の 2 つの要素を持つタプル型として定義されます。

複数の異なる型

一つのタプル内で複数の異なる型を持つことができます。これは配列ではできないことで、タプルが特に役立つケースです。以下に例を示します。

// /components/DisplayUserInfo.tsx
type User = [string, number, Date]

export const DisplayUserInfo = ({ user }: { user: User }) => {
  const [name, age, registrationDate] = user
  return (
    <div>
      <p>{`Name: ${name}`}</p>
      <p>{`Age: ${age}`}</p>
      <p>{`Registered since: ${registrationDate.toISOString()}`}</p>
    </div>
  )
}

上記の例では、ユーザー情報を保持する User 型をタプル型で定義しています。User 型は、文字列の名前、数値の年齢、そして Date オブジェクトの登録日を順番に持つタプル型です。

Next.js で、タプル型を実装

components/TupleDisplay.tsx

import { CSSProperties } from 'react'

type TupleDisplayProps = {
  data: [string, number, CSSProperties]
}

const TupleDisplay = ({ data }: TupleDisplayProps) => {
  const [text, number, style] = data

  return (
    <div style={style}>
      <h1>{text}</h1>
      <h2>{number}</h2>
    </div>
  )
}

export default TupleDisplay

ここではTupleDisplayという React コンポーネントを作成しています。このコンポーネントは、プロパティとしてタプル型のdataを受け取ります。このdatastringnumberCSSPropertiesの三つの異なる型の要素を持つタプルです。

コンポーネント内部で、配列の分割代入を用いてdataから各要素を取り出しています。そして、取り出したtextnumberを表示し、styleを div 要素のスタイルとして適用しています。

pages/index.tsx

import TupleDisplay from '../components/TupleDisplay'

const Home = () => {
  const tupleData: [string, number, React.CSSProperties] = ['Hello Tuple', 12345, { color: 'red' }]

  return <TupleDisplay data={tupleData} />
}

export default Home

次に、このTupleDisplayコンポーネントをHomeコンポーネントから呼び出しています。tupleDataというタプルを定義し、その要素を'Hello Tuple'12345{ color: 'red' }としています。このtupleDataTupleDisplayコンポーネントのdataプロパティとして渡しています。

これにより、TupleDisplayコンポーネントは受け取ったタプルの各要素をそれぞれ適切に扱い、表示することができます。

タプル型の応用例

タプル型は上記のような単純なケースだけでなく、より複雑なケースでも活用することができます。例えば、異なる型の値のペアを多数管理する場合などに便利です。

type Pair = [string, number]

let pairs: Pair[] = []

pairs.push(['one', 1])
pairs.push(['two', 2])

pairs.forEach(([text, number]) => {
  console.log(`${text}: ${number}`)
})

このコードでは、まずPairというタプル型を定義し、その配列pairsを作成しています。そして、配列に対してpushメソッドを使ってペアを追加し、その後forEachメソッドを使って各ペアを出力しています。

このように、タプル型を活用することで、異なる型の要素を組み合わせて管理することが容易になります。

まとめ

以上のように、タプル型は TypeScript で提供されている便利な機能の一つです。異なる型の値をまとめて扱うことができるので、様々なケースで活用できます。Next.js と組み合わせることで、より複雑なデータ構造の管理にも役立ちます。

Next.js と TypeScript で、タプル型を学ぶ