Next.js と TypeScript で、props の最適な渡し方を理解する

TwitterFacebookHatena

TL;DR

この記事では、Next.js と TypeScript を用いて、props の効果的な渡し方を紹介します。具体的には、どのようにしてコンポーネント間でデータを渡すのか、そしてそれがなぜ重要なのかを解説します。

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

データの流れとは?

React では、データは props を介してコンポーネント間で流れます。props は、親コンポーネントから子コンポーネントへのデータの一方通行の橋となります。だからこそ、このデータの流れを正確に制御することは、アプリケーションの効率的な動作を実現する上で重要な要素となるわけなんですよ。

type Props = {
  message: string
}

const Greeting = ({ message }: Props) => {
  return <h1>{message}</h1>
}

上記のコードは、簡単な Greeting コンポーネントを示しています。このコンポーネントは、message という文字列の props を受け取り、その内容を表示します。

Next.js で、props を実装

では、Next.js と TypeScript を使用して、props をどのように渡すのか見ていきましょう。まずはシンプルなケースから始めます。

ファイル名:pages/index.tsx

import Greeting from '../components/Greeting'

export default function Home() {
  return (
    <div>
      <Greeting message="Hello, Next.js!" />
    </div>
  )
}

この例では、Home コンポーネントから Greeting コンポーネントへと、props を介してデータが渡されています。具体的には、message という props を使って "Hello, Next.js!" という文字列が Greeting コンポーネントに渡され、それが表示されます。

props の活用

さて、props を用いてデータを渡すことの真価は、複数の異なる値を同じコンポーネントに渡す際に発揮されます。

ファイル名:pages/index.tsx

import Greeting from '../components/Greeting'

export default function Home() {
  return (
    <div>
      <Greeting message="Hello, Next.js!" />
      <Greeting message="こんにちは、Next.js!" />
    </div>
  )
}

このケースでは、2 つの異なるメッセージが同じ Greeting コンポーネントに渡されています。これが可能なのは、props のおかげですね。つまり、props を活用することで、コンポーネントの再利用性が大幅に向上します。

Emotion で実装

さらに、CSS-in-JS ライブラリの一つである Emotion を使えば、props を用いてスタイルを動的に変更することも可能です。これにより、コンポーネントの見た目を柔軟に制御することができるんですね。

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

type Props = {
  message: string
  color: string
}

const Greeting = ({ message, color }: Props) => {
  return (
    <h1
      css={css`
        color: ${color};
      `}
    >
      {message}
    </h1>
  )
}

props と children の違い

React では、propschildren はコンポーネント間でデータを渡す基本的な方法ですが、それぞれ異なる用途と特性を持っています。

props は一方通行のデータフローを提供し、親コンポーネントから子コンポーネントへデータを渡します。一方、children は親コンポーネントから子コンポーネントへのコンテンツのパッシングを可能にします。これは、親が子の内部コンテンツを制御するための方法として使用されます。

「親コンポーネントから子コンポーネントへのコンテンツのパッシング」は、基本的に親コンポーネントが子コンポーネントにデータを送信することを意味します。これは一般的に props を通じて行われ、React における一方向データフローの中核をなす概念です。

しかし、ここで特に言及されているのは children prop によるコンテンツのパッシングです。これは特殊なケースで、コンポーネントが他のコンポーネント内にネストされる場合に利用されます。親コンポーネントは children prop を通じて、子コンポーネント内部に直接コンテンツ(文字列、JSX、他のコンポーネントなど)を挿入できます。

例えば、以下のような場合です。

<ParentComponent>
  <ChildComponent />
</ParentComponent>

この例で、<ChildComponent />ParentComponentchildren となります。これは ParentComponent から ChildComponent へのコンテンツのパッシングの一例です。

props と children の違いをまとめると以下のようになります。

特性 props children
データフロー 親コンポーネントから子コンポーネントへの一方向 親コンポーネントから子コンポーネントへのコンテンツの受け渡し
用途 コンポーネントの再利用とカスタマイズを可能にします 子コンポーネントの内部コンテンツを親コンポーネントから制御する
任意の型(文字列、数値、関数、オブジェクトなど) React ノード(文字列、数値、JSX エレメント、関数コンポーネントなど)
必要性 コンポーネント定義による 任意(コンポーネント定義によるが、デフォルトでは任意)

この表は、props と children の一般的な特性を示しています。具体的な使い方や制限は、実装するコンポーネントの具体的な要件によります。しかし、これらの概念の理解は、React のコンポーネント設計の基礎となります。

以下の例を見てみましょう。

type PanelProps = {
  title: string
  children: React.ReactNode
}

const Panel = ({ title, children }: PanelProps) => {
  return (
    <div>
      <h2>{title}</h2>
      {children}
    </div>
  )
}

この Panel コンポーネントは、title という名前の props と children を受け取ります。title は単純な文字列で、パネルのタイトルを定義します。一方、children はパネルの内部に表示する任意のコンテンツを受け取ります。

この Panel コンポーネントは以下のように使用することができます。

<Panel title="Important">
  <p>This is some important content!</p>
</Panel>

type の代わりに interface を使って同じコンポーネントを定義すると、以下のようになります。

interface を使用した例

interface PanelProps {
  title: string
  children: React.ReactNode
}

const Panel = ({ title, children }: PanelProps) => {
  return (
    <div>
      <h2>{title}</h2>
      {children}
    </div>
  )
}

ここでも、Panel コンポーネントは titlechildren を受け取り、それぞれを適切な場所にレンダリングしています。

全ての props を一度に渡す

全ての props を一度に子コンポーネントに渡す場合、スプレッド構文を使用します。これは JavaScript の機能で、オブジェクトの全てのキーと値を別のオブジェクトにコピーします。これを使用して、親コンポーネントから子コンポーネントに全ての props を一度に渡すことができます。

interface AllProps {
  title: string
  subtitle: string
  content: string
}

const ParentComponent = (props: AllProps) => {
  return <ChildComponent {...props} />
}

const ChildComponent = ({ title, subtitle, content }: AllProps) => {
  return (
    <div>
      <h1>{title}</h1>
      <h2>{subtitle}</h2>
      <p>{content}</p>
    </div>
  )
}

このコードでは、ParentComponent が持っている全ての props(この場合、title, subtitle, content)を ChildComponent に渡しています。これは、props を一つ一つ渡すのではなく、全てを一度に渡すためのショートカットです。

ただし、全ての props を一度に渡すことは便利ではありますが、どの props が実際に渡されているのかが明確でなくなる場合があります。そのため、使用する際には注意が必要です。必要な props だけを明示的に渡すことで、コードの可読性を高めることができます。

こういった形で、typeinterface のどちらを使うかは主にプロジェクトやチームのスタイルガイドに依存します。両方とも TypeScript での型定義に使われますが、一部の機能や記法が異なることを理解しておくと良いでしょう。

以上が propschildren の違いになります。どちらも React コンポーネント設計の基礎となる概念なので、理解と活用は必須ですね。

今回学習した propsは、Next.js で頻出します。必ずマスターしましょう。

Next.js と TypeScript で、props の最適な渡し方を理解する