今回やること
前回は、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というエラーが表示されています。
ListScreenからitemsを参照できないため、エラーが表示されています。
itemsはAppの中で定義しているため、
別のコンポーネントであるListScreenからそのまま使うことはできません。
そこで、Appが持っているitemsを
ListScreenへ渡してみましょう。
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から、
子となるListScreenへitemsを渡しました。
<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では、
onCreateとonEditの両方を受け取ります。
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で判断しています。
実際の画面の中身は、
ListScreen、CreateScreen、
EditScreenが担当します。
このように役割ごとにコンポーネントへ分けると、
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で保存して完成