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

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

今回やること

前回は、items配列をmap()で処理して、 複数のデータをテーブルへ一覧表示しました。
ここまで、一覧画面のJSXはすべてAppの中に書いてきました。

しかし、画面の中身が増えるにつれて、 Appのコードも長くなってきます。
そこで今回は、一覧画面を別のコンポーネントとして分けてみます。

あわせて、コンポーネントへ値や関数を渡すために使う propsについても確認します。
今回はまだファイルは分けず、すべてApp.jsxの中に書きます。

スポンサーリンク

コンポーネントとは?

Reactでは、画面のまとまりを コンポーネント として分けることができます。
実は、これまで使ってきたAppもコンポーネントです。

Reactのコンポーネント名は大文字から始めます。
まずは、一覧画面を担当するListScreenコンポーネントを作ってみましょう。

1. ListScreenコンポーネントを作ってみよう

まずはコンポーネントの形を確認するために、 「ListScreenコンポーネントです」と表示する小さな ListScreenを作ります。

Appコンポーネントの下に、次のListScreenを追加してください。

function ListScreen() {
  return (
    <main className="list-screen">
      <p>ListScreenコンポーネントです</p>
    </main>
  );
}

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

<ListScreen />

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

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

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

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

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

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

  return <ListScreen />;
}

ブラウザでListScreenを確認しよう

ファイルを保存して、ブラウザを確認してみましょう。
「ListScreenコンポーネントです」と表示されれば、 AppからListScreenを表示できています。

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

2. 一覧画面の中身を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>

      <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でエラーを確認しよう

この状態でファイルを保存してみましょう。
今回は、一覧画面が正しく表示されずエラーになります。

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

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

itemsAppの中で定義しているため、 別のコンポーネントであるListScreenからそのまま使うことはできません。
そこで、Appが持っているitemsListScreenへ渡してみましょう。

3. itemsをListScreenへ渡そう

コンポーネントへ値を渡すときは、 Reactのpropsという仕組みを使います。
まずは実際にitemsを渡してみましょう。

Appからitemsを渡す

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

return <ListScreen items={items} />;

ListScreenでitemsを受け取る

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

function ListScreen({ items }) {
  // 一覧画面のJSX
}

ブラウザで一覧表示が戻ったことを確認しよう

ファイルを保存して、ブラウザを確認してみましょう。
前回と同じように2件のデータがテーブルへ表示されれば、 itemsを正しく渡せています。

propsとは?

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

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

<ListScreen items={items} />

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

ListScreenでは、受け取ったpropsから itemsを取り出しています。

function ListScreen({ items }) {
  // ...
}

4. 関数もpropsで渡してみよう

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

screen Stateを持っているのは ListScreenではなくAppです。
そこで、画面を切り替えるための関数もpropsとして ListScreenへ渡します。

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

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

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

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

function ListScreen({ items, onCreate }) {
  // 一覧画面のJSX
}

「新規登録」ボタンのonClickに、 受け取ったonCreateを指定します。

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

編集画面へ切り替える関数も渡す

編集画面への切り替えも同じ方法です。
AppからonEditを渡します。

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

ListScreenでは、 onCreateonEditの両方を受け取ります。

function ListScreen({ items, onCreate, onEdit }) {
  // 一覧画面のJSX
}

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

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

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

新規登録・編集ボタンを確認しよう

ファイルを保存してブラウザを確認し、 「新規登録」をクリックすると新規登録画面、 「編集」をクリックすると編集画面へ切り替わればOKです。

5. 新規登録・編集画面もコンポーネントに分けよう

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

CreateScreenコンポーネントを作る

新規登録画面は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では、次のようにCreateScreenを表示します。

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

EditScreenコンポーネントを作る

編集画面も同じように、 EditScreenコンポーネントへ分けます。
今回はまだ編集フォームを作らないので、 画面名と「一覧へ」ボタンだけ用意します。

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では、次のようにEditScreenを表示します。

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>

      <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で保存して完成
スポンサーリンク