コードを役割ごとに整理しよう

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

今回やること

前回は、一覧・新規登録・編集の3画面を ListScreenCreateScreenEditScreenというコンポーネントに分けました。

ただし、3つのコンポーネントはまだ すべてApp.jsxの中に書かれています。

今回は、増えてきたコードを 役割ごとのファイルへ整理します。

今回はコードの置き場所を整理する回なので、 アプリの見た目や動作は変えません。

スポンサーリンク

今回のファイル構成を確認する

今回は、最終的に次のような構成にします。

src/
├─ App.jsx
├─ components/
│  ├─ ListScreen.jsx
│  ├─ CreateScreen.jsx
│  └─ EditScreen.jsx
├─ initialItems.js
└─ options.js

それぞれのファイルには、次の役割を持たせます。

  • App.jsx:画面切り替えを管理する
  • components:コンポーネントファイルを置くフォルダ
  • initialItems.js:一覧に最初から表示するデータ
  • options.js:新規登録画面や編集画面で使う選択肢

JSXを書くコンポーネントのファイルは.jsx、 JavaScriptのファイルは.jsにしてください。

まずは、前回作った ListScreenコンポーネントを別ファイルへ移してみましょう。

ListScreenコンポーネントを別ファイルへ移す

srcの中にcomponentsフォルダを作り、 その中にListScreen.jsxを作成します。

前回App.jsxの下側に書いた ListScreenコンポーネントの定義を、 ListScreen.jsxファイルへ移します。

別ファイルから使えるようにするため、 今回はコンポーネントの先頭に export defaultを付けます。

src/components/ListScreen.jsx

export default 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>
  );
}

移したあとは、 App.jsxに書いてあった ListScreenの定義は削除してください。

exportで別のファイルから使えるようにする

JavaScriptでは、あるファイルで定義した値や関数を 別のファイルから使えるようにするとき、 exportを使います。

今回は、ListScreenを 別のファイルから使えるようにするため、 次のように書いています。

export default function ListScreen(...) {
  ...
}

export defaultしたものは、 別のファイルからimportして使えます。

App.jsxからListScreenを読み込む

ListScreenコンポーネントを別ファイルへ移したので、 今度はApp.jsxから読み込みます。

App.jsxの先頭に、次の1行を追加します。

import ListScreen from "./components/ListScreen";

これで、別ファイルにあるListScreenコンポーネントを App.jsxから使えるようになります。

App側の使い方は、前回と同じです。

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

ファイルを分けても、 itemsや関数をpropsで渡す流れは変わりません。

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

新規登録画面も別ファイルへ移す

次に、前回App.jsxに書いた CreateScreenコンポーネントも別ファイルへ移します。

src/components/CreateScreen.jsxを作成し、 CreateScreenの定義を App.jsxから移動してください。

こちらも別ファイルから使えるように、 export defaultを付けます。

src/components/CreateScreen.jsx

export default 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>
  );
}

移したあとは、 App.jsxに書いてあった CreateScreenの定義を削除します。

そして、App.jsxの先頭で読み込みます。

import CreateScreen from "./components/CreateScreen";

Appからの使い方は前回と同じです。

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

編集画面も別ファイルへ移す

編集画面も同じように、 前回App.jsxに書いた EditScreenコンポーネントを別ファイルへ移します。

src/components/EditScreen.jsxを作成し、 EditScreenの定義を移動してください。

こちらも先頭にexport defaultを付けます。

src/components/EditScreen.jsx

export default 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.jsxに書いてあった EditScreenの定義を削除します。

App.jsxの先頭で読み込みます。

import EditScreen from "./components/EditScreen";

これで、3つの画面コンポーネントを App.jsxから別ファイルへ移せました。

ファイルを保存して、 「新規登録」「編集」「一覧へ」で 3画面をこれまでと同じように切り替えられることを確認してみましょう。

初期データも別ファイルへ移す

次は、これまでAppの中に置いていた初期データも別ファイルへ整理します。

srcの中にinitialItems.jsを作成し、App.jsxにある初期データを移しましょう。

変数名はinitialItemsとしてください。

src/initialItems.js

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

移したあとは、 App.jsxに書いてあった初期データは削除します。

ここではexport defaultではなく、 export constを使っています。

App.jsxでは、 次のように{}を使って読み込みます。

import { initialItems } from "./initialItems";

App.jsxで初期データを使う

読み込んだinitialItemsを、 一覧画面へ渡すデータとして使います。

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

初期データの置き場所は変わりましたが、 ListScreenにはこれまでと同じように propsでデータを渡しています。

ファイルを保存してブラウザを確認し、 2件のデータがこれまでと同じように表示されればOKです。

次回以降で使う選択肢も整理しておく

次回は、新規登録画面にフォームを作ります。

その後の編集画面でも、 カテゴリやステータスには同じ選択肢を使います。

同じ選択肢をそれぞれの画面に直接書くのではなく、 共通して使えるように options.jsへまとめておきましょう。

src/options.js

export const categories = [
  "本",
  "漫画",
  "映画",
  "アニメ",
  "ゲーム",
  "音楽",
  "家具",
  "その他",
];

export const statuses = [
  "とりあえずメモ",
  "気になる",
  "欲しい",
  "検討中",
  "購入済",
];

categoriesにはカテゴリの選択肢、 statusesにはステータスの選択肢をまとめています。

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

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

今回登場したReactとJavaScriptの要素
種類 今回登場したもの
React 別ファイルに分けたコンポーネントを組み合わせて使う
JavaScript exportimport、JavaScriptモジュール

コンポーネントを組み合わせて画面を作る部分はReactです。

一方、別ファイルの値や関数を読み書きする exportimportはJavaScriptの機能です。

今回のまとめ

  • 3つの画面コンポーネントをcomponentsフォルダへ分けた
  • exportで別ファイルから使えるようにした
  • importで別ファイルのコンポーネントやデータを読み込んだ
  • 初期データをinitialItems.jsへ分けた
  • 次回以降で共通利用する選択肢をoptions.jsへまとめた
  • ファイルを分けてもpropsやStateのデータの流れは変わらない
  • 見た目や動作を変えずにコードを役割ごとに整理した

今回の完成コード

今回、作成・変更したファイルの完成コードです。
※ファイル名やファイルを置く場所を間違えないように注意してください

src/App.jsx

import { useState } from "react";

import ListScreen from "./components/ListScreen";
import CreateScreen from "./components/CreateScreen";
import EditScreen from "./components/EditScreen";
import { initialItems } from "./initialItems";

export default function App() {
  const [screen, setScreen] = useState("list");

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

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

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

src/components/ListScreen.jsx

export default 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>
  );
}

src/components/CreateScreen.jsx

export default 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>
  );
}

src/components/EditScreen.jsx

export default 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>
  );
}

src/initialItems.js

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

src/options.js

export const categories = [
  "本",
  "漫画",
  "映画",
  "アニメ",
  "ゲーム",
  "音楽",
  "家具",
  "その他",
];

export const statuses = [
  "とりあえずメモ",
  "気になる",
  "欲しい",
  "検討中",
  "購入済",
];

アプリの完成状況

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