一覧画面をコンポーネントに分けよう

← 第5
Reactアプリ作成(JS編)6 / 15
7回 →

今回やること

前回は、items配列をmap()で処理して、 複数のデータをテーブルへ一覧表示しました。

ここまで、一覧画面のJSXはすべてAppの中に書いてきました。 しかし、画面の中身が増えるにつれて、Appのコードも長くなってきます。

そこで今回は、一覧画面を 別のコンポーネントとして分けてみます。

あわせて、コンポーネントへ値や関数を渡すために使う propsについても確認します。

今回はまだファイルは分けず、 すべてApp.jsxの中に書きます。

スポンサーリンク

コンポーネントとは?

Reactでは、画面のまとまりを コンポーネント として分けることができます。

実は、これまで使ってきたAppもコンポーネントです。

まずはコンポーネントの形を確認するために、 一覧画面の名前だけを表示する小さな ListScreenコンポーネントを作ってみましょう。

Appコンポーネントの下に、次のListScreenを追加します。

function ListScreen() {
  return (
    <main className="list-screen">
      <h2 className="screen-title">一覧画面</h2>
    </main>
  );
}

関数でコンポーネントを作る場合は、 画面に表示したいJSXをreturnします。

また、Reactのコンポーネント名は 大文字から始めます。

作ったコンポーネントは、JSXの中で次のように使えます。

<ListScreen />

HTMLのタグによく似ていますが、 大文字から始まるListScreenは 自分で作ったReactコンポーネントです。

まずはListScreenを表示してみよう

先ほど作ったListScreenを、 Appから表示してみましょう。

一覧画面を表示する最後のreturnを、 一度次のように変更します。

export default function App() {
  const items = [
    // 前回と同じため省略
  ];

  const [screen, setScreen] = useState("list");

  if (screen === "create") {
    // 前回と同じため省略
  }

  if (screen === "edit") {
    // 前回と同じため省略
  }

  return <ListScreen />;
}

これで、Appから ListScreenコンポーネントを表示できます。

ここで一度ファイルを保存して、ブラウザを確認してみましょう。 「一覧画面」と表示されればOKです。

まだ元の一覧画面の中身は移していないため、 この時点ではテーブルやボタンは表示されません。

一覧画面の中身をListScreenへ移す

コンポーネントの使い方が分かったので、 次は実際の一覧画面の中身を ListScreenへ移していきます。

前回までAppに書いていた mainから一覧テーブルまでを、 ListScreenへ移します。

function ListScreen() {
  return (
    <main className="list-screen">
      <header className="list-header">
        <h1>気になるものリスト</h1>
        <div className="header-actions">
          <button type="button">
            新規登録
          </button>
          <button type="button">
            編集
          </button>
        </div>
      </header>

      <h2 className="screen-title">一覧画面</h2>

      <table>
        <thead>
          <tr>
            <th>名前</th>
            <th>カテゴリ</th>
            <th>ステータス</th>
            <th>価格</th>
            <th>メモ</th>
          </tr>
        </thead>
        <tbody>
          {items.map((item) => (
            <tr key={item.id}>
              <td>{item.name}</td>
              <td>{item.category}</td>
              <td>{item.status}</td>
              <td>{item.price}円</td>
              <td>{item.memo}</td>
            </tr>
          ))}
        </tbody>
      </table>
    </main>
  );
}

一覧画面のまとまりをListScreenとして分けることで、 Appは「どの画面を表示するか」という役割に集中しやすくなります。

いったん、この状態でファイルを保存して、ブラウザを確認してみましょう。

今回は、一覧画面が正しく表示されず、エラーになります。

ブラウザの開発者ツールでConsoleを確認すると、items is not definedというエラーが表示されています。

開発者ツールのConsoleにitems is not definedと表示された画面
ListScreenからitemsを参照できないため、エラーが表示されています。

itemsAppの中で定義しているため、別のコンポーネントであるListScreenからそのまま使うことはできず、エラーが発生しています。

そこで、Appが持っているitemsListScreenへ渡す必要があります。

propsで値を渡す

コンポーネントへ値を渡すときに使うのが propsです。

propsは、親のコンポーネントから 子のコンポーネントへ情報を渡すための仕組みです。

今回は、親となるAppから、 子となるListScreenitemsを渡します。

Appからitemsを渡す

値を渡す側のAppでは、次のように書きます。

return <ListScreen items={items} />

左側のitemsがpropsの名前、右側の{items}が実際に渡す値です。

ListScreenでitemsを受け取る

値を受け取るListScreenでは、次のように書きます。

function ListScreen({ items }) {
  // 前回と同じため省略
}

これで、Appから渡されたitemsListScreenの中で使えます。

ここでも一度ファイルを保存して、ブラウザを確認してみましょう。

前回と同じように2件のデータがテーブルへ表示されれば、 itemsをpropsで正しく渡せています。

関数もpropsで渡せる

一覧画面は表示できましたが、今の「新規登録」「編集」ボタンにはまだ画面切り替えの処理がありません。

screen Stateを持っているのはListScreenではなくAppです。

そこで、画面を切り替えるための関数もpropsとしてListScreenへ渡します。

新規登録画面へ切り替える関数を渡す

Appから、新規登録画面へ切り替える関数をonCreateという名前で渡します。

return <ListScreen
  items={items}
  onCreate={() => setScreen("create")}
/>

ListScreenでは、 onCreateを受け取ります。

function ListScreen({ items, onCreate }) {
  // 前回と同じため省略
}

そして、ListScreenの「新規登録」ボタンのonClickonCreateを指定します。

<button type="button" onClick={onCreate}>
  新規登録
</button>

ボタンをクリックすると、 propsで受け取ったonCreateが実行されます。

その結果、Appscreen Stateが "create"へ変わります。

このように、propsでは値だけでなく 関数も渡すことができます。

編集ボタンもpropsで動かす

編集画面への切り替えも、同じ方法で作れます。

AppからonEditを渡します。

return <ListScreen
  items={items}
  onCreate={() => setScreen("create")}
  onEdit={() => setScreen("edit")}
/>

ListScreenでは、 onEditも受け取ります。

function ListScreen({ items, onCreate, onEdit }) {
  // 前回と同じため省略
}

ListScreenの「編集」ボタンのonClickには、 onEditを指定します。

<button type="button" onClick={onEdit}>
  編集
</button>

ファイルを保存してブラウザを確認し、 「新規登録」「編集」でこれまでと同じように画面が切り替わればOKです。

新規登録画面もコンポーネントにする

一覧画面をコンポーネントに分ける流れが分かったので、新規登録画面も同じように分けておきましょう。

新規登録画面はCreateScreenコンポーネントにします。
ListScreenの下に記載してください。

function CreateScreen({ onBack }) {
  return (
    <main className="form-screen">
      <header className="screen-header">
        <h1>気になるものリスト</h1>
        <div className="header-actions">
          <button type="button" className="secondary" onClick={onBack}>
            一覧へ
          </button>
        </div>
      </header>

      <h2 className="screen-title">新規登録画面</h2>
    </main>
  );
}

「一覧へ」ボタンで使う関数は、 onBackというpropsで受け取っています。

Appでは、次のように使います。

if (screen === "create") {
  return <CreateScreen onBack={() => setScreen("list")} />;
}

編集画面もコンポーネントにする

編集画面も同じように、EditScreenコンポーネントへ分けます。
先程のCreateScreenの下に記載してください。

今回はまだ編集フォームを作らないので、画面名と「一覧へ」ボタンだけ用意します。

function EditScreen({ onBack }) {
  return (
    <main className="form-screen">
      <header className="screen-header">
        <h1>気になるものリスト</h1>
        <div className="header-actions">
          <button type="button" className="secondary" onClick={onBack}>
            一覧へ
          </button>
        </div>
      </header>

      <h2 className="screen-title edit-title">編集画面</h2>
    </main>
  );
}

Appでは、次のように使います。

if (screen === "edit") {
  return <EditScreen onBack={() => setScreen("list")} />;
}

これで、一覧・新規登録・編集の3画面が それぞれ別のコンポーネントになりました。

最後にブラウザで3つの画面を切り替えてみましょう。 「新規登録」「編集」「一覧へ」がすべてこれまでと同じように動けばOKです。

今回はコードの構造を変えましたが、 画面の見た目や動作そのものは変わりません。

Appの役割を確認する

3つの画面をコンポーネントに分けると、 Appの役割も見やすくなります。

export default function App() {
  const items = [
    // 前回と同じため省略
  ];

  const [screen, setScreen] = useState("list");

  if (screen === "create") {
    return <CreateScreen onBack={() => setScreen("list")} />;
  }

  if (screen === "edit") {
    return <EditScreen onBack={() => setScreen("list")} />;
  }

  return (
    <ListScreen
      items={items}
      onCreate={() => setScreen("create")}
      onEdit={() => setScreen("edit")}
    />
  );
}

Appでは、 どの画面を表示するかをscreen Stateで判断しています。

そして、実際の画面の中身は ListScreenCreateScreenEditScreenが担当します。

このように役割ごとにコンポーネントへ分けると、 1つのコンポーネントにすべてのコードを書くより、 コードの役割を追いやすくなります。

ReactとJavaScriptを分けて見てみよう

今回登場した内容を、ReactとJavaScriptに分けて整理します。

今回登場したReactとJavaScriptの要素
種類 今回登場したもの
React コンポーネント、props
JavaScript 関数、分割代入、関数を値として渡す

コンポーネントやpropsはReactの仕組みです。

一方、functionや分割代入、 関数を値として渡すこと自体はJavaScriptの機能です。

今回のまとめ

  • Reactでは画面のまとまりをコンポーネントとして分けられる
  • コンポーネント名は大文字から始める
  • 一覧画面をListScreenコンポーネントへ分けた
  • propsを使って親から子へ値を渡せる
  • 関数もpropsとして渡せる
  • 新規登録画面をCreateScreenへ分けた
  • 編集画面をEditScreenへ分けた
  • コンポーネント化とファイル分割は別の考え方

今回の完成コード

今回のApp.jsxの完成コードです。 今回はまだファイルを分けず、 3つの画面コンポーネントを同じファイルの中に定義します。

src/App.jsx

import { useState } from "react";

export default function App() {
  const items = [
    {
      id: "sample-1",
      name: "はじめてのWebデザイン本",
      category: "本",
      status: "気になる",
      price: 2200,
      memo: "図が多くて読みやすそう",
    },
    {
      id: "sample-2",
      name: "小さなサイドテーブル",
      category: "家具",
      status: "欲しい",
      price: 3500,
      memo: "部屋に置きやすそう",
    },
  ];

  const [screen, setScreen] = useState("list");

  if (screen === "create") {
    return <CreateScreen onBack={() => setScreen("list")} />;
  }

  if (screen === "edit") {
    return <EditScreen onBack={() => setScreen("list")} />;
  }

  return (
    <ListScreen
      items={items}
      onCreate={() => setScreen("create")}
      onEdit={() => setScreen("edit")}
    />
  );
}

function ListScreen({ items, onCreate, onEdit }) {
  return (
    <main className="list-screen">
      <header className="list-header">
        <h1>気になるものリスト</h1>
        <div className="header-actions">
          <button type="button" onClick={onCreate}>
            新規登録
          </button>
          <button type="button" onClick={onEdit}>
            編集
          </button>
        </div>
      </header>

      <h2 className="screen-title">一覧画面</h2>

      <table>
        <thead>
          <tr>
            <th>名前</th>
            <th>カテゴリ</th>
            <th>ステータス</th>
            <th>価格</th>
            <th>メモ</th>
          </tr>
        </thead>
        <tbody>
          {items.map((item) => (
            <tr key={item.id}>
              <td>{item.name}</td>
              <td>{item.category}</td>
              <td>{item.status}</td>
              <td>{item.price}円</td>
              <td>{item.memo}</td>
            </tr>
          ))}
        </tbody>
      </table>
    </main>
  );
}

function CreateScreen({ onBack }) {
  return (
    <main className="form-screen">
      <header className="screen-header">
        <h1>気になるものリスト</h1>
        <div className="header-actions">
          <button type="button" className="secondary" onClick={onBack}>
            一覧へ
          </button>
        </div>
      </header>

      <h2 className="screen-title">新規登録画面</h2>
    </main>
  );
}

function EditScreen({ onBack }) {
  return (
    <main className="form-screen">
      <header className="screen-header">
        <h1>気になるものリスト</h1>
        <div className="header-actions">
          <button type="button" className="secondary" onClick={onBack}>
            一覧へ
          </button>
        </div>
      </header>

      <h2 className="screen-title edit-title">編集画面</h2>
    </main>
  );
}

アプリの完成状況

  • ✅ Reactを動かし、JSXを表示する
  • ✅ Stateで画面を切り替える
  • ✅ 1件のデータを表示する
  • ✅ 複数のデータを一覧表示する
  • ✅ コンポーネントとpropsを使う
  • ⬜ コンポーネントを別ファイルへ分ける
  • ⬜ 新規登録画面を作る
  • ⬜ 一覧データをStateで管理する
  • ⬜ 編集画面を作る
  • ⬜ データを更新する
  • ⬜ データを削除する
  • ⬜ キーワードで検索する
  • ⬜ カテゴリ・ステータスで絞り込む
  • ⬜ localStorageで保存して完成
スポンサーリンク