Next.js と TypeScript で学ぶ、interface の様々なパターン

TwitterFacebookHatena

TL;DR

このページでは、TypeScript の interface の実装方法について解説しますね。一言でいうと interface とは、型を定義するための仕組みです。

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

TypeScript の interface とは?

TypeScript の interface は、型の一種です。プロパティやメソッドの名前と型を組み合わせて新しい型を定義するための仕組みです。これにより、オブジェクトの形状を規定し、エディターやコンパイラーがエラーを検出するための情報を提供します。

では、以下に基本的な interface の使い方について見ていきましょう。

interface Person {
  name: string
  age: number
}

const p: Person = {
  name: 'John Doe',
  age: 30,
}

このコードでは、Personという interface を定義しています。この interface はnameageというプロパティを持つオブジェクトの型を規定します。このPerson型の変数pを定義し、その値としてnameが"John Doe"、ageが 30 のオブジェクトを指定しています。

Optional プロパティを持つ Interface

TypeScript では Interface 内のプロパティを任意にすることができます。プロパティ名の後ろに?を付けることで、そのプロパティは存在してもしなくても良いことを示します。これは主にオプションのプロパティを持つオブジェクトに有用です。

interface User {
  id: string
  name: string
  age?: number // Optional property
}

Read-Only プロパティを持つ Interface

TypeScript の Interface は、プロパティを読み取り専用にすることもできます。これはプロパティの値が一度設定された後に変更されることを防ぐためです。読み取り専用のプロパティを定義するためには、プロパティ名の前にreadonly修飾子を付けます。

interface Config {
  readonly id: string
  name: string
}

関数型を持つ Interface

TypeScript の Interface は、関数の型も定義することができます。これは主に特定の形状の関数を期待する関数やメソッドの引数に対して使用されます。

interface Greeter {
  (name: string, age: number): string
}

const greet: Greeter = (name, age) => {
  return `Hello, ${name}. You are ${age} years old.`
}

Indexable Types を持つ Interface

TypeScript の Interface は、インデックス可能な型も定義することができます。これは主に、一部または全部が動的に設定されるオブジェクトに対して使用されます。

interface StringArray {
  [index: number]: string
}

const myArray: StringArray = ['Bob', 'Fred']

クラスと Interface

TypeScript の Interface は、クラスが特定の契約を満たすことを強制するためにも使用できます。Interface を実装するには、クラス定義の後にimplementsキーワードと Interface の名前を追加します。

interface ClockInterface {
  currentTime: Date
  setTime(d: Date): void
}

class Clock implements ClockInterface {
  currentTime: Date = new Date()
  setTime(d: Date) {
    this.currentTime = d
  }
}

ハイブリッド型を持つ Interface

Interface は混合型(またはハイブリッド型)を記述することもできます。これは、オブジェクトが関数であると同時にプロパティを持つことができる JavaScript の特性を反映したものです。

interface Counter {
  (start: number): string
  interval: number
  reset(): void
}

function getCounter(): Counter {
  let counter = function (start: number) {} as Counter
  counter.interval = 123
  counter.reset = function () {}
  return counter
}

let c = getCounter()
c(10)
c.reset()
c.interval = 5.0

Interface の拡張

Interface は他の Interface を拡張することができます。これにより、複数の Interface が同じ基本プロパティを共有している場合に、コードの重複を避けることができます。

interface Shape {
  color: string
}

interface Square extends Shape {
  sideLength: number
}

let square = {} as Square
square.color = 'blue'
square.sideLength = 10

Interface と型別名の相互適用

Interface と型別名(type aliases)は組み合わせて使用することができます。これにより、より複雑な型の記述が可能となります。

type Point = {
  x: number
  y: number
}

interface Circle {
  center: Point
  radius: number
}

let myCircle: Circle = {
  center: { x: 0, y: 0 },
  radius: 5,
}

Interface の合成

Interface は複数の Interface を 1 つに合成することも可能です。この機能はクラスが複数の Interface を実装できるようにすることで特に有用です。

interface Animal {
  breathe(): void
}

interface Mammal {
  feedMilk(): void
}

interface Human extends Animal, Mammal {
  speak(): void
}

class Person implements Human {
  breathe() {
    console.log('Breathing...')
  }

  feedMilk() {
    console.log('Feeding milk...')
  }

  speak() {
    console.log('Speaking...')
  }
}

インターフェイスを使用したオブジェクトの配列

TypeScript では、interfaceを使用してカスタム型を定義することができます。これは、オブジェクトが特定の形状(つまり、特定のプロパティとその型)を持つことを保証するための強力な方法です。これを配列と組み合わせて使用すると、オブジェクトの配列を効果的に型付けすることができます。

以下の例では、Userというインターフェイスを定義し、その配列をusersという名前の変数で宣言しています。

interface User {
  id: number
  name: string
}

const users: User[] = [
  { id: 1, name: 'Alice' },
  { id: 2, name: 'Bob' },
  // more users...
]

インターフェイスで配列を含むオブジェクトの定義

インターフェイスを使用して、配列をプロパティとして持つオブジェクトを定義することも可能です。以下の例では、Companyというインターフェイスにemployeesというプロパティを持たせ、その型をUser[]と定義しています。

interface User {
  id: number
  name: string
}

interface Company {
  name: string
  employees: User[]
}

const company: Company = {
  name: 'OpenAI',
  employees: [
    { id: 1, name: 'Alice' },
    { id: 2, name: 'Bob' },
    // more employees...
  ],
}

Next.js で、TypeScript の interface を実装

では、次に Next.js のプロジェクトで TypeScript の interface をどのように使うかについて見ていきましょう。

まずは、Next.js のページコンポーネントを作成します。以下のコードはpages/index.tsxというファイルで定義します。

import type { NextPage } from 'next'
import { UserProfile } from '../components/UserProfile'

type User = {
  name: string
  age: number
}

const HomePage: NextPage = () => {
  const user: User = {
    name: 'John Doe',
    age: 30,
  }

  return <UserProfile user={user} />
}

export default HomePage

このコードでは、まずUserという interface を定義しています。これは先程と同じように、ユーザーを表すオブジェクトの型を規定します。次にHomePageというページコンポーネントを定義し、その中でuserというUser型の変数を定義し、値としてユーザーの情報を持つオブジェクトを指定しています。そして、このuserUserProfileというコンポーネントに渡しています。

コンポーネントのプロパティの型定義

先程作成したUserProfileコンポーネントの中身を見てみましょう。以下のコードはcomponents/UserProfile.tsxというファイルで定義します。

type User = {
  name: string
  age: number
}

type Props = {
  user: User
}

const UserProfile = ({ user }: Props) => {
  return (
    <div>
      <p>Name: {user.name}</p>
      <p>Age: {user.age}</p>
    </div>
  )
}

export { UserProfile }

ここでもまたUserという interface を定義していますが、このコンポーネントが受け取るプロパティの型として、更にPropsという interface を定義しています。このPropsuserというプロパティを持つことを規定し、その型は先程定義したUserとなります。そして、このProps型を引数の型として指定し、UserProfileコンポーネントを定義しています。このコンポーネントでは、受け取ったuserの情報を表示します。

コンポーネント間のデータの型定義

次に、異なるコンポーネント間でデータを渡す際の型定義の例を見てみましょう。以下のコードはcomponents/UserList.tsxというファイルで定義します。

import { UserProfile } from './UserProfile'

type User = {
  id: number
  name: string
  age: number
}

type Props = {
  users: User[]
}

const UserList = ({ users }: Props) => {
  return (
    <div>
      {users.map((user) => (
        <UserProfile key={user.id} user={user} />
      ))}
    </div>
  )
}

export { UserList }

このコードでは、複数のユーザー情報を持つ配列を受け取り、その各ユーザーについてUserProfileコンポーネントを表示します。これにより、複数のユーザー情報を一覧として表示することができます。

users: User[] はどういう意味?

User[]は User 型の配列を表します。つまり、usersは User 型のオブジェクトが複数含まれる配列を指しています。

まず、最初のtype Userの部分では、Userという新たな型を定義しています。このUser型は、id(数値)、name(文字列)、age(数値)というプロパティを持つオブジェクトです。

次に、type Propsの部分では、Propsという新たな型を定義しています。このProps型は、User[]型のusersというプロパティを持つオブジェクトです。User[]は「User 型の配列」という意味になります。つまり、usersプロパティは User 型のオブジェクトが複数含まれる配列を表すことになります。

以下のようなデータ構造を想像するとわかりやすいかもしれません。

let someProps: Props = {
  users: [
    { id: 1, name: 'Alice', age: 20 },
    { id: 2, name: 'Bob', age: 25 },
    { id: 3, name: 'Charlie', age: 30 },
    // more users...
  ],
}

上記の例では、somePropsProps型であり、そのusersプロパティは 3 つのUser型のオブジェクトを含む配列です。

Next.js と TypeScript で学ぶ、interface の様々なパターン