今回やること
前回は、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というエラーが表示されています。
ListScreenからitemsを参照できないため、エラーが表示されています。
itemsはAppの中で定義しているため、別のコンポーネントであるListScreenからそのまま使うことはできず、エラーが発生しています。
そこで、Appが持っているitemsを
ListScreenへ渡す必要があります。
propsで値を渡す
コンポーネントへ値を渡すときに使うのが
propsです。
propsは、親のコンポーネントから
子のコンポーネントへ情報を渡すための仕組みです。
今回は、親となるAppから、
子となるListScreenへ
itemsを渡します。
Appからitemsを渡す
値を渡す側のAppでは、次のように書きます。
return <ListScreen items={items} />
左側のitemsがpropsの名前、右側の{items}が実際に渡す値です。
ListScreenでitemsを受け取る
値を受け取るListScreenでは、次のように書きます。
function ListScreen({ items }) {
// 前回と同じため省略
}
これで、Appから渡されたitemsをListScreenの中で使えます。
ここでも一度ファイルを保存して、ブラウザを確認してみましょう。
前回と同じように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の「新規登録」ボタンのonClickに
onCreateを指定します。
<button type="button" onClick={onCreate}>
新規登録
</button>
ボタンをクリックすると、
propsで受け取ったonCreateが実行されます。
その結果、Appのscreen 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で判断しています。
そして、実際の画面の中身は
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>
<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で保存して完成