Next.jsによるWebアプリケーション開発概論

TypeScriptによるProps型定義

5Next.js基礎:ReactコンポーネントとJSX・Propsの基本
Next.jsTailwind CSSアプリ開発開発Web開発

この節で学ぶこと

この節では、React の props に TypeScript で型を付ける意味 と、最初に使うべき基本パターン を学びます。

React の props は親コンポーネントから子コンポーネントへ渡すデータで、React 公式では props はコンポーネントの唯一の引数であり、通常は分割代入で読むと説明されています。

一方、Next.js は TypeScript を built-in でサポートしており、create-next-app で新規作成すると必要パッケージと推奨設定が自動で整います。

この節の到達目標は4つです。

  1. props に型を付けると「何を受け取る部品か」が明確になると説明できることです。
  2. typeinterface を使って props の形を定義できることです。
  3. 必須 props、任意 props、children、デフォルト値の基本を理解することです。
  4. union type を使って props の取り得る値を安全に表現できることです。TypeScript 公式は type alias、interface、optional properties、union types を Everyday Types の中心項目として説明しています。

なぜ Props に型を付けるのか

受け取れるデータを先に決めるため

props は便利ですが、何でも渡せてしまう状態のままだと、使う側と作る側の認識がずれやすくなります。React 公式でも、親は props を JSX に追加し、子はそれを読み取る、という流れで UI を組み立てます。

ここに TypeScript を入れると、このコンポーネントは何を受け取るのか をコード上で明示できます。

たとえば、何も型を付けないと次のようになります。

function Greeting({ name }) {
  return <p>こんにちは、{name}さん</p>
}

見た目は簡単ですが、name が文字列なのか、数値なのか、そもそも必須なのかが分かりません。

TypeScript を使うと、こう書けます。

type GreetingProps = {
  name: string
}

function Greeting({ name }: GreetingProps) {
  return <p>こんにちは、{name}さん</p>
}

この瞬間、Greeting は「name という文字列を受け取る部品」になります。

React TypeScript Cheatsheet でも、通常の関数として props 型を受け取り、戻り値は JSX 要素として扱える例が示されています。

間違いに早く気づけるため

TypeScript 公式は、union type や object type の仕組みを通して、値の形に合わないものを早い段階で検出できることを説明しています。

props に型を付けると、たとえば次のような間違いをエディタやビルド時点で見つけやすくなります。

type GreetingProps = {
  name: string
}

function Greeting({ name }: GreetingProps) {
  return <p>こんにちは、{name}さん</p>
}

export default function Page() {
  return <Greeting name={123} />
}

この 123string ではないので、型エラーになります。

実行して「なんか変だな」と気づくのではなく、書いた時点で止めてもらえる。これが props 型定義の大きな価値です。TypeScript 公式は、構造に合った値だけが許される、という考え方を一貫して採っています。

Props 型定義の基本形

type を使う

TypeScript 公式では、type は任意の型に名前を付ける仕組みだと説明しています。特にオブジェクト型へ名前を付ける用途は非常に一般的です。

props では、まずこの形が最も分かりやすいです。

type ButtonProps = {
  label: string
}

function Button({ label }: ButtonProps) {
  return <button>{label}</button>
}

このコードはとても素直です。

ButtonProps という名前で props の形を定義し、その型を関数引数に付けています。これだけで「label は文字列で必須」と分かります。

interface を使う

TypeScript 公式では、interface も object type に名前を付ける方法であり、多くの場合 type とほぼ同じように使えると説明しています。違いとしては、interface は拡張や再オープンが可能です。

interface ButtonProps {
  label: string
}

function Button({ label }: ButtonProps) {
  return <button>{label}</button>
}

typeinterface のどちらでも props 型定義はできます。

TypeScript 公式のヒューリスティックでは、必要になるまで interface を使う方針も紹介されていますが、どちらか一方だけが正しいわけではありません。

最初はどちらを使えばよいか

初心者向けには、まず type で始めても十分です。

理由は単純で、見た目が短く、union type や intersection まで同じ記法でつながりやすいからです。とはいえ、外部公開する型や拡張前提の設計では interface を選ぶことも普通にあります。TypeScript 公式も、両者は多くの場面で自由に選べると説明しています。

分割代入と Props 型定義

React では分割代入がよく使われる

React 公式では、props は props オブジェクト全体で受け取ることもできるが、通常は分割代入で function Avatar({ person, size }) のように書くと説明しています。

TypeScript を加えると、次の形になります。

type AvatarProps = {
  person: {
    name: string
    imageId: string
  }
  size: number
}

function Avatar({ person, size }: AvatarProps) {
  return (
    <img
      src={`/images/${person.imageId}.jpg`}
      alt={person.name}
      width={size}
      height={size}
    />
  )
}

この書き方の利点は、関数の入り口で「何を受け取るか」がすぐ見えることです。

React 公式の props 解説とも相性がよく、TypeScript の object type とも自然につながります。

props オブジェクトで受けてもよい

もちろん、次のように書いてもかまいません。

type AvatarProps = {
  person: {
    name: string
    imageId: string
  }
  size: number
}

function Avatar(props: AvatarProps) {
  return (
    <img
      src={`/images/${props.person.imageId}.jpg`}
      alt={props.person.name}
      width={props.size}
      height={props.size}
    />
  )
}

React 公式も、props は実際には1つの props オブジェクトとして渡されると説明しています。

最初はこの形の方が理解しやすいこともあります。慣れてきたら分割代入へ移る、でも十分です。

必須 Props と任意 Props

必須 props

TypeScript で普通にプロパティを書くと、その props は必須になります。

つまり、渡し忘れるとエラーになります。

type BadgeProps = {
  text: string
}

function Badge({ text }: BadgeProps) {
  return <span>{text}</span>
}

この場合、<Badge /> はエラーで、<Badge text="新着" /> は正しい使い方です。

TypeScript の object type は、必要なプロパティを満たしているかどうかで判定されます。

任意 props

TypeScript 公式では、optional properties は ? を付けて表現すると説明しています。

props でも同じです。

type BadgeProps = {
  text: string
  color?: string
}

function Badge({ text, color }: BadgeProps) {
  return <span>{text} {color}</span>
}

この場合、color はあってもなくてもよい props です。

TypeScript 公式は、optional property を読むときは undefined の可能性を考える必要があると説明しています。

optional props の扱いに注意する

たとえば、次のようなコードは危険です。

type UserProps = {
  name: string
  nickname?: string
}

function UserCard({ name, nickname }: UserProps) {
  return <p>{name}({nickname.toUpperCase()})</p>
}

nickname が未指定だと undefined になり得るので、そのまま toUpperCase() は安全ではありません。

TypeScript 公式も、optional property を読むときは undefined を考慮するよう説明しています。

安全に書くなら、こうです。

type UserProps = {
  name: string
  nickname?: string
}

function UserCard({ name, nickname }: UserProps) {
  return (
    <p>
      {name}
      {nickname ? `(${nickname.toUpperCase()})` : ''}
    </p>
  )
}

Props のデフォルト値と TypeScript

デフォルト値は分割代入で書ける

React 公式では、props のデフォルト値は分割代入で size = 100 のように書けると説明しています。 missing または undefined のときだけ適用され、null0 には適用されません。

TypeScript でもこの書き方はそのまま使えます。

type AvatarProps = {
  name: string
  size?: number
}

function Avatar({ name, size = 100 }: AvatarProps) {
  return (
    <div>
      <p>{name}</p>
      <p>{size}px</p>
    </div>
  )
}

この形はとても実用的です。

size は任意にしておきつつ、未指定なら 100 を使う、という柔らかい API にできます。React 公式の default value の考え方と一致します。

nullundefined は違う

ここは初心者が引っかかりやすい点です。React 公式では、デフォルト値が使われるのは props が missing または undefined のときだけで、null0 では使われないと明記しています。

type AvatarProps = {
  size?: number | null
}

function Avatar({ size = 100 }: AvatarProps) {
  return <p>{size}</p>
}

この場合、

  • <Avatar />100
  • <Avatar size={undefined} />100
  • <Avatar size={null} />null
  • <Avatar size={0} />0 になります。

この違いは、フォームや API レスポンスを扱うときにかなり効きます。

children の型定義

children は特別な props

React 公式では、JSX タグの中にネストした内容は children props として親コンポーネントへ渡されると説明しています。

TypeScript では、これにも型を付けられます。

type CardProps = {
  children: React.ReactNode
}

function Card({ children }: CardProps) {
  return <div className="card">{children}</div>
}

この React.ReactNode は、「React が子要素として描画できるもの」を表す型としてよく使われます。

React TypeScript Cheatsheet でも、コンポーネントの戻り値や props の書き方を通常関数ベースで整理しており、children を明示的に型定義する流れと相性がよいです。

React.FC に頼らず children を明示する

React TypeScript Cheatsheet では、現在の一般的な見解として React.FunctionComponent / React.FC は必須ではなく、普通の関数コンポーネントで十分だと説明しています。React 18 型定義以降は children が自動で暗黙追加されない点も背景にあります。

したがって、次のように明示する方が分かりやすいです。

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

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

これは、ユーザーのご要望とも相性がよいです。

通常の関数 + 型定義で、何を受け取るかがはっきり見えるからです。

type と interface をどう使い分けるか

どちらでも props は書ける

TypeScript 公式では、typeinterface は多くの場面で似ており、自由に選べるケースが多いと説明しています。

props でも両方使えます。

type ButtonProps = {
  label: string
}
interface ButtonProps {
  label: string
}

この2つは、props 型定義としてはどちらも普通に成立します。

迷ったらこう考える

TypeScript 公式のヒューリスティックでは、「必要になるまで interface を使う」という考え方も紹介されていますが、最終的には個人やチームの方針次第です。

初心者向けには次のように考えると分かりやすいです。

  • type union type や intersection も含めて柔軟に書きやすい
  • interface オブジェクトの形を名前付きで定義する感覚が強い 今の段階では、どちらか一方に極端にこだわる必要はありません。

まずは「props の形を型で明示できる」ことが重要です。

Union Type を使った Props 型定義

値の候補を絞る

TypeScript 公式では、union type は「いくつかの型のどれか」を表現する仕組みだと説明しています。

props でも非常によく使います。

type ButtonProps = {
  variant: 'primary' | 'secondary'
  label: string
}

function Button({ variant, label }: ButtonProps) {
  return <button>{variant}: {label}</button>
}

この場合、variant には 'primary''secondary' しか入れられません。

つまり、props の「自由すぎる入力」を適度に絞れます。これはデザインシステムや UI 部品設計でとても強いです。

union type が役立つ場面

たとえば、サイズを自由な文字列にしたくないときに使えます。

type BadgeProps = {
  size: 'sm' | 'md' | 'lg'
  text: string
}

function Badge({ size, text }: BadgeProps) {
  return <span>{size}: {text}</span>
}

TypeScript 公式は、union type のメンバーに共通しない操作はそのままでは使えず、必要に応じて絞り込みが必要だと説明しています。

props 設計では、その性質がむしろ利点になります。想定外の値を弾けるからです。

実践的な Props 型定義の例

商品カード

type ProductCardProps = {
  title: string
  price: number
  isSoldOut?: boolean
}

function ProductCard({
  title,
  price,
  isSoldOut = false,
}: ProductCardProps) {
  return (
    <article>
      <h2>{title}</h2>
      <p>{price}円</p>
      <p>{isSoldOut ? '売り切れ' : '在庫あり'}</p>
    </article>
  )
}

この例には、必須 props、任意 props、デフォルト値が全部入っています。

初心者が props 型定義を学ぶには、とてもよい練習形です。

レイアウト用ラッパー

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

function Section({ title, children }: SectionProps) {
  return (
    <section>
      <h2>{title}</h2>
      <div>{children}</div>
    </section>
  )
}

この例では children の型定義を確認できます。

Next.js の layout.tsx や、共通 UI の部品設計にもつながりやすい形です。

Next.js での意味

Next.js は TypeScript を built-in サポート

Next.js 公式では、Next.js は built-in TypeScript を持ち、create-next-app で新規作成すると必要パッケージと適切な設定が自動で整うと説明しています。

既存プロジェクトでも .ts / .tsx に変更し、next devnext build を走らせると tsconfig.json と依存関係が追加されます。

page.tsxcomponents/Button.tsx のようなファイルで、普通に型付き props を使うのが今の自然な書き方です。

IDE 補完とも相性がよい

Next.js 公式では、Next.js には custom TypeScript plugin と type checker があり、VS Code などで高度な型チェックやオートコンプリートに使えると説明しています。

props に型を付けると、エディタ上で次のような恩恵が得られます。

  • 渡し忘れを見つけやすい
  • 型違いを見つけやすい
  • 補完候補が出る
  • リファクタリングしやすい

初心者がつまずきやすい点

children を型定義し忘れる

React 18 以降の型定義では、React.FC に頼らず通常関数で書くなら、children は自分で明示する必要があります。React TypeScript Cheatsheet も、React.FC は必須ではなく普通の関数で十分だと整理しています。

optional props を普通の値のように使う

TypeScript 公式の optional property の説明どおり、? が付いた props は undefined の可能性があります。

そのままメソッドを呼ぶと危険です。条件分岐やデフォルト値を使う方が安全です。

union type に対して勝手にメソッドを呼ぶ

TypeScript 公式では、union type に対してはすべてのメンバーで共通する操作しか、そのままでは使えないと説明しています。必要なら narrowing が要ります。

props でも同じで、string | number に対していきなり toUpperCase() はできません。

ミニ練習問題

問1

props に TypeScript で型を付ける主な利点として最も適切なものはどれですか。

A. CSS が自動生成される

B. コンポーネントが受け取る値の形を明示できる

C. 画像が自動圧縮される

D. package.json が不要になる

答え

B

解説

TypeScript の object type や type alias を使うと、コンポーネントが受け取る props の形を明示できます。これにより、渡し忘れや型違いを早く検出しやすくなります。

問2

次のうち、optional props の書き方として正しいものはどれですか。

A. color: string? B. color?: string C. ?color: string D. color = string?

答え

B

解説

TypeScript 公式では、optional property はプロパティ名の後ろに ? を付けて表現すると説明しています。

問3

React の children について正しいものはどれですか。

A. 画像 URL 専用の props

B. ネストした JSX が渡される特別な props

C. 必ず文字列型になる props

D. next/image でのみ使える props

答え

B

解説

React 公式では、コンポーネントタグの中にネストした内容は children props として親へ渡されると説明しています。

問4

次の型定義のうち、variant'primary' または 'secondary' に限定できるものはどれですか。

A. variant: string B. variant?: number C. variant: 'primary' | 'secondary' D. variant: any

答え

C

解説

TypeScript 公式では、union type は複数の型や値候補のいずれかを表現できます。文字列リテラルの union を使うと、props の候補を限定できます。

問5

Next.js における TypeScript の説明として正しいものはどれですか。

A. TypeScript は公式非対応である

B. create-next-app では TypeScript の設定は一切行われない

C. Next.js は built-in TypeScript を持ち、新規作成時に必要設定を自動で整える

D. TypeScript は public フォルダでしか使えない

答え

C

解説

Next.js 公式では、Next.js comes with built-in TypeScript であり、create-next-app で新しいプロジェクトを作ると必要パッケージと適切な設定が自動で整うと説明しています。

まとめ

TypeScript による props 型定義は、React コンポーネントを ただ動く部品 から 何を受け取るかが明確な部品 へ変えるための仕組みです。typeinterface で props の形を定義し、必須項目、任意項目、デフォルト値、children、union type を組み合わせることで、コンポーネント API をかなり整理できます。React 公式の props の考え方と、TypeScript 公式の object type・optional property・union type の考え方が、ここで自然につながります。

この節で特に押さえたいのは、次の5点です。

  • props は親から子へ渡すデータである。
  • TypeScript で props の形を明示すると、使い方のズレを減らせる。
  • optional props は ? で表し、undefined を考慮する。
  • children は明示的に型を付けると分かりやすい。
  • union type を使うと props の候補を安全に絞れる。

ここが分かると、React コンポーネントは一段引き締まって見えてきます。

「受け取るものが曖昧な部品」ではなく、「契約が見える部品」になるからです。次の節でコンポーネント分割と再利用設計へ進むと、この props 型定義がさらに生きてきます。

参考文献

  • React Docs, Passing Props to a Component
  • Next.js Docs, Configuration: TypeScript
  • TypeScript Docs, Everyday Types
  • React TypeScript Cheatsheet, Function Components
教材トップへ戻る