Next.js と TypeScript で、Union 型を学ぶ

TwitterFacebookHatena

TL;DR

このページでは、Next.js と TypeScript の組み合わせにおける Union 型の活用方法について解説します。コードの可読性と保守性を向上させるテクニックとして、TypeScript の Union 型の知識は不可欠です。

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

Union 型とは何か

Union 型は、TypeScript の型システムの一部で、一つの値が複数の型のいずれかを取りうることを表現します。たとえば、以下のように定義できます。

type Name = string | null

この型は、Nameが文字列または null となることを表しています。

Next.js における Union 型の利用

以下のコードは、Next.js のコンポーネントで Union 型を活用したシンプルな例ですね。

// components/Button.tsx

type Props = {
  variant: 'primary' | 'secondary'
  children: React.ReactNode
}

const Button = ({ variant, children }: Props) => {
  const buttonStyle = variant === 'primary' ? 'bg-blue-500' : 'bg-green-500'

  return <button className={buttonStyle}>{children}</button>
}

この Button コンポーネントは、variantプロパティを使って表示スタイルを切り替えます。variantの型は、Union 型の 'primary' | 'secondary' で、この二つの値のみを許容します。これにより、想定外のスタイル指定を事前に排除でき、安全にコンポーネントを利用することができます。

Union 型を活用した高度なテクニック

さらに複雑なシナリオでは、Union 型を使って異なるプロパティの組み合わせをハンドリングすることもできます。以下にその一例をお見せします。

// components/ComplexComponent.tsx

type BaseProps = {
  children: React.ReactNode
}

type VariantAProps = BaseProps & {
  variant: 'A'
  propA: string
}

type VariantBProps = BaseProps & {
  variant: 'B'
  propB: number
}

type Props = VariantAProps | VariantBProps

const ComplexComponent = (props: Props) => {
  switch (props.variant) {
    case 'A':
      return <div>{props.propA}</div>
    case 'B':
      return <div>{props.propB}</div>
  }
}

この ComplexComponent コンポーネントは、プロパティ variant の値に応じて異なるプロパティを必要とします。Union 型を活用することで、このような動的なプロパティセットを型安全に扱うことができます。

Emotion での Union 型の利用

Emotion を使う場合も、Union 型を活用することでスタイルのバリエーションを効果的に扱うことができます。以下にその一例をお見せします。

/** @jsxImportSource @emotion/react */
import { css } from '@emotion/react'

type Variant = 'info' | 'warning'

type Props = {
  variant: Variant
  children: React.ReactNode
}

const variantStyles: Record<Variant, ReturnType<typeof css>> = {
  info: css`
    background-color: lightblue;
    color: darkblue;
  `,
  warning: css`
    background-color: lightyellow;
    color: darkred;
  `,
}

const Alert = ({ variant, children }: Props) => {
  return <div css={variantStyles[variant]}>{children}</div>
}

この Alert コンポーネントは、variantプロパティにより表示スタイルを切り替えます。Union 型を使って variant の値を制限し、それに対応するスタイルを定義します。Emotion の css 関数を使って、それぞれのスタイルを切り替えます。

Discriminated Unions

ここでは、TypeScript の Union 型と Discriminated Unions(判別される Union 型)を使って、異なるステータスを持つ非同期データを取り扱う方法について説明します。このテクニックは、API リクエストの結果を扱う際に特に役立ちます。

まず、以下のように API リクエストのステータスを表現する型を定義します。

type DataStatus =
  | { status: 'loading' } //
  | { status: 'success'; data: any }
  | { status: 'error'; error: Error }

ここでは、3 つのステータスを表現しています:

  • データがまだロード中である(loading
  • データのロードが成功し、データが利用可能である(success
  • データのロードがエラーとなった(error

次に、この型を使って非同期データを取り扱う React コンポーネントを作成します:

import React from 'react'

type DataStatus =
  | { status: 'loading' } //
  | { status: 'success'; data: any }
  | { status: 'error'; error: Error }

type DataComponentProps = {
  dataStatus: DataStatus
}

const DataComponent = ({ dataStatus }: DataComponentProps) => {
  switch (dataStatus.status) {
    case 'loading':
      return <div>Loading...</div>
    case 'success':
      return <div>{JSON.stringify(dataStatus.data)}</div>
    case 'error':
      return <div>Error: {dataStatus.error.message}</div>
  }
}

このコンポーネントは dataStatus プロパティに基づいて異なる描画を行います。このプロパティは上で定義した DataStatus 型を持つため、TypeScript は switch 文内でそれぞれのケースに対する型を正しく推論することができます。これにより、例えば dataStatus.dataloadingerror のステータスで誤ってアクセスすると、TypeScript はこれをエラーとして報告します。

このように、TypeScript の Union 型と Discriminated Unions を使用することで、非同期データの取り扱いを型安全に行うことができます。これは API リクエストやその他の非同期処理を行う React コンポーネントで非常に役立つテクニックです。

以上が、Next.js と TypeScript における Union 型の利用法です。型の安全性を保ちながら、コードの可読性と保守性を向上させるために、Union 型を適切に活用することが重要です。

Next.js と TypeScript で、Union 型を学ぶ