React Routerとアプリ設計の導入スライド
URLが変わらない代償
いまの fleama は、画面を切り替えても URL がずっと同じままです。
画面を切り替えても URL が変わらない
気に入った商品を人に教えられない
戻るボタンを押す
アプリごと閉じてしまう
開き直す
必ず最初の画面に戻る
画面と URL が結び付いていないのが原因です。
1 / 5
React Routerとアプリ設計 - ルーティング導入
移動してカートを見る
移動の書き方を切り替えて、カートに入れた点数がどうなるか見比べてください。
- a href にすると、サーバーへ行き、カートの点数が捨てられる
- Link to のままだと、サーバーへ行かず、点数がそのまま残る
- カートに入れた点数を動かすと、残る点数もその値に変わる
2 / 5
対応表を書く
一番外側を BrowserRouter で包み、Routes と Route で対応表を書きます。
path="/"URLelement={<Home />}出す部品3 / 5
考えてみよう外しても進めます
React Router で押した移動は、サーバーへどう振る舞いますか。
選ぶと、この枚の中で答えと理由を確かめられます。
4 / 5
URL の変化を見て、出す部品を差し替える
画面と URL を結び付けるしくみです。
5 / 5
対応表に行を足す
ページを増やすというのは、この対応表に行を足すことです。
path="/items"新しい行1 / 5
React Routerとアプリ設計 - RoutesとRoute
URLとRouteの対応
開いた URL を切り替えて、対応表のどの行で止まるか追ってください。
- どの行にも当たらない URL を選ぶと、画面に何も出なくなる
- 当たる URL を選ぶと、その行だけが合うになり、他は素通りされる
- ナビを Routes の中に書くを入れると、ナビが消える
2 / 5
ナビをどこに置くか
同じナビでも、置く場所で共通か切り替わる部分かが決まります。
Routes の外に置く
全ページで共通のまま出続ける
Routes の中に置く
ページを移るたびに消えたり出たりする
取り違えると、ページを移るたびにナビが消えるアプリになります。
3 / 5
elementはJSXで渡す
element に渡すのは書かれた JSX です。関数のまま渡すと何も出ません。
以前の書き方
component={ItemList}v7の書き方
element={<ItemList />}ItemList とだけ書いて渡しても、何も出ません。
4 / 5
考えてみよう外しても進めます
Routes は、中に並んだ Route のうちいくつを選んで出しますか。
選ぶと、この枚の中で答えと理由を確かめられます。
5 / 5
商品の数だけ書けない
出品されるたびに Route を1行足す、というわけにはいきません。
商品ごとに書く
<Route path="/items/1" ... />
<Route path="/items/2" ... />
<Route path="/items/3" ... />1本のRoute
<Route
path="/items/:id"
element={<ItemDetail />}
/>右の1行で、どの id が来ても同じ部品が受け取ります。
1 / 5
React Routerとアプリ設計 - URLパラメータ
idを動かして確かめる
URL の id を動かして、同じ Route から別の商品が出るのを見てください。
- アドレスバーと useParams の値と一致した行が、一緒に変わる
- 出品されていない id にすると、一致する商品が無くなる
- Item の id を number で持つにすると、文字列と一致しなくなる
2 / 5
静かに一致しなくなる
型を number にしても動いてしまうので、気づきにくいまま壊れます。
Item の id を number で持つ
文字列と一致せず、find が常に undefined になる
見つからないときの分岐を書かない
item.title を読んだところで止まる
Route の id と useParams の名前をずらす
受け取る値が undefined のまま残る
URL から来る値は必ず文字列です。
3 / 5
React Routerとアプリ設計 - URLパラメータ
見つかるかで分ける
見つかるかどうかで、画面に出す中身を分けてください。
find は見つからないと undefined を返します。
| 場面 | 対応 |
|---|---|
| ある | 商品名と価格を出す |
| 無い | 見つかりませんでした、と出す |
| number型 | find が常に一致しない |
4 / 5
考えてみよう外しても進めます
/items/2 を開いたとき、useParams が返す id の型は何ですか。
選ぶと、この枚の中で答えと理由を確かめられます。
5 / 5
URLかstateか
検索語を useState だけに持つと、URL は /items のままで打った言葉が乗りません。
stateだけに持つ
const [keyword, setKeyword] =
useState("");
// URL は /items のままURLに置く
const [searchParams] =
useSearchParams();
const keyword =
searchParams.get("q") ?? "";右は読み込み直しても、貼って送っても同じ絞り込みが再現できます。
1 / 5
React Routerとアプリ設計 - 検索条件をURLに
置き場所で相手が変わる
検索欄に打った言葉を選んでから、置き場所を state に変えて相手の画面を見てください。
- URL に置くと、自分の画面と相手の画面が同じ件数にそろう
- 絞り込みを state だけで持つに変えると、URL が動かず相手には伝わらない
- 0件になる言葉を選ぶと、どちらの置き場所でも0件になる
2 / 5
getは無いとnull
searchParams.get は値が無いとき null を返すので、空文字にそろえて使います。
setSearchParamsURL を書き換える?? ""無いときは空文字にする3 / 5
React Routerとアプリ設計 - 検索条件をURLに
何をURLに置くか
人に送ったときに同じ画面が見えてほしいものだけを URL に置きます。
| 置くもの | 置かないもの |
|---|---|
| 検索語 | 入力途中のフォーム |
| 並び順 | 開いているかどうかのメニュー |
| ページ番号 | 一時的な選択状態 |
4 / 5
考えてみよう外しても進めます
検索した一覧を、そのまま人に送って同じ画面を見せたいとき、検索語をどこに置きますか。
選ぶと、この枚の中で答えと理由を確かめられます。
5 / 5
Routesは対応表
Routes はいまの URL に一番よく合う Route を1つだけ選びます。
URLを開く
打ち間違えた URL や古いリンクが開かれる
対応表を見比べる
並んだ Route の中から一番よく合う行を探す
受ける行が無い
どの行にも当たらず、中身が空のまま残る
1 / 5
React Routerとアプリ設計 - 404ページ
どこまで線が届くか
開いた URL を切り替えて、線がどの行に届くか追ってください。
- 打ち間違えた URL は、どの行にも当たらずアスタリスクの行が受ける
- アスタリスクの行を消すと、受け皿が無くなり中身が空のまま残る
- きちんと書いた URL は、それぞれの行にそのまま当たる
2 / 5
アスタリスクで受ける
path にアスタリスクを書くと、他のどれにも当たらなかったとき用の行になります。
path="*"どれにも当たらないとき3 / 5
受け皿には出口を
404 のページで大事なのは、謝る文ではなく次にどこへ行けるかです。
出口が無い
return (
<h1>準備中</h1>
);Linkで出口を置く
return (
<>
<h1>見つかりません</h1>
<Link to="/">トップへ</Link>
</>
);戻るボタンしか出口が無いと、そのまま離脱されます。
4 / 5
考えてみよう外しても進めます
どの Route にも当たらなかった URL を受ける行の path はどれですか。
選ぶと、この枚の中で答えと理由を確かめられます。
5 / 5
React Routerとアプリ設計 - 見た目と機能の分離
2つの仕事を持つ部品
いまの ItemList は、通信の仕事と表示の仕事を1つの関数の中でやっています。
1つの関数が2つの理由で書き換わる状態になっています。
| 仕事 | 中身 |
|---|---|
| 通信の仕事 | fetch と state の管理 |
| 表示の仕事 | JSX とクラス名で並べる |
1 / 5
React Routerとアプリ設計 - 見た目と機能の分離
取る係と並べる係
取れた商品の数を動かして、同じ数が上から下へ渡っていくのを見てください。
- 件数を動かすと、渡った数がそのまま画面まで届く
- 表示担当に fetch を書くを入れると、表示担当の枠が朱色になる
- そのとき表示担当の中に useState と fetch が並ぶ
2 / 5
分けると変わるコード
表示担当からは state と通信が消え、props だけを受け取る形になります。
1つの関数でやる
function ItemList() {
const [items, setItems] =
useState([]);
useEffect(() => {
fetch("/api/items");
}, []);
return <div>{}</div>;
}表示担当を切り出す
export function ItemListView({
items,
}: Props) {
return <div className="list">
{items.map()}
</div>;
}3 / 5
考えてみよう外しても進めます
表示担当の ItemListView に書いてはいけないものはどれですか。
選ぶと、この枚の中で答えと理由を確かめられます。
4 / 5
取る係と並べる係を分けると、同じ並べ方を別の一覧にも使い回せる
分けておくと、見た目だけ直したいときに得をします。
5 / 5
分岐が増える行き止まり
Layout に真偽値の props を足すたび、Layout が呼び出し元をぜんぶ知ることになります。
横に絞り込みを出したい
showSide と sideKind が増える
別の画面が増える
また真偽値と分岐が1本増える
枠が呼び出し元を知るほど、枠は使い回せなくなります。
1 / 5
React Routerとアプリ設計 - 合成で組み立てる
分岐か、穴を空けるか
Layout を使う画面の数を増やして、上と下の増え方を見比べてください。
- 画面が増えるたび、上の分岐は1本ずつ伸びる
- 下の穴は3つのままで増えない
- 画面が増えすぎると上の枠が朱色になり、知りすぎたことが分かる
2 / 5
穴の型を決める
真偽値の props を、ReactNode の穴に置き換えます。
真偽値で分岐する
type LayoutProps = {
title: string;
showSide: boolean;
sideKind: string;
};穴を空けて受け取る
type LayoutProps = {
header: ReactNode;
side: ReactNode;
children: ReactNode;
};穴が2つ以上あるときは、真ん中の主役を children にします。
3 / 5
考えてみよう外しても進めます
Layout は、中に入る CategoryNav や ItemCard を知っている必要がありますか。
選ぶと、この枚の中で答えと理由を確かめられます。
4 / 5
枠が中身を知るほど、枠は使い回せなくなる
真偽値の props が増えそうになったら、まず穴に置き換えられないか考えます。
5 / 5
まとめ方で変わる
同じ fleama のファイルを、種類別と機能別で置き直すとこう変わります。
種類でまとめる
components/
ItemList.tsx
Cart.tsx
hooks/
useCart.ts
types/
cart.ts機能でまとめる
features/
items/
ItemList.tsx
cart/
Cart.tsx
useCart.ts
types.ts1 / 5
React Routerとアプリ設計 - 機能別フォルダ
用事ごとに開く場所
直す用事を切り替えて、開くファイルがどこに散るか見てください。
- 数量を足すにすると、種類別は3つのフォルダを行き来する
- 機能ごと消すにすると、機能別はフォルダごと消せる
- 並べ方を変えるでも、機能別は関係するファイルが近くにある
2 / 5
React Routerとアプリ設計 - 機能別フォルダ
置き場所の見分け方
その部品が fleama の商品を知っているかどうかで決めます。
知っていれば features、知らなければ components です。
| 部品 | 置き場所 |
|---|---|
| Layout | components。商品を知らない |
| ItemList | features。商品を知っている |
| Cart | features。商品を知っている |
| ボタンの枠 | components。商品を知らない |
3 / 5
importの道のり
移すと、呼ぶ側の道のりが変わります。型も features の下から読みます。
./features/items/ItemList機能のフォルダを指す4 / 5
考えてみよう外しても進めます
features に入れないのは、どんな部品ですか。
選ぶと、この枚の中で答えと理由を確かめられます。
5 / 5
書き換える項目を絞る
商品を丸ごと送り直すと、送り忘れた項目が消えます。
商品を丸ごと送る
await fetch(url, {
method: "PUT",
body: JSON.stringify(item),
});statusだけ送る
await fetch(url, {
method: "PATCH",
body: JSON.stringify({
status: "sold",
}),
});送る項目を絞るほど、他の項目が消える事故が減ります。
1 / 5
React Routerとアプリ設計 - 購入フロー
送るときの3点セット
図解は POST の例ですが、method と headers と body の3点は PATCH でも同じです。1つずつ外してください。
- method を外すと GET として送られてしまう
- headers を外すと相手が形式を読み取れない
- body にそのままオブジェクトを渡すと、読めない文字列が届く
2 / 5
考えてみよう外しても進めます
PATCH で status だけ送ると、商品名と価格はどうなりますか。
選ぶと、この枚の中で答えと理由を確かめられます。
3 / 5
購入完了までの4手
購入するを押してから完了が出るまでを、順番に見ます。
購入するを押す
handleBuy が呼ばれる
PATCHを送る
status だけを sold にして送る
返事で差し替え
items の同じ id を新しい商品に置き換える
完了を表示
売り切れとして画面に出る
4 / 5
考えてみよう外しても進めます
返ってきた商品をそのまま items に使うのはなぜですか。
選ぶと、この枚の中で答えと理由を確かめられます。
5 / 5
React Routerとアプリ設計 - 完成と次のステップ
身についた6つの力
fleama を作りながら、できるようになったことを振り返ります。
| 身につけた力 | fleama でやったこと |
|---|---|
| 型 | props も state もイベントも型で守った |
| 取得 | 読み込み中とエラーと空を全部持たせた |
| フック | 取得もフォームも画面から切り離した |
| Context | テーマとユーザーをどこからでも読めるようにした |
| reducer | カートの操作を型で網羅した |
| ルーティング | 5つの画面を URL で分けた |
1 / 5
次に進む2つの道
何を優先したいかで、次に進む道が変わります。
Node.js入門
偽物のデータを本物のサーバーに置きかえる。残したい人はこちら
Next.js入門
画面をサーバー側で先に組み立てる。公開したい人はこちら
どちらから進んでも、fleama の画面はそのまま活かせます。
2 / 5
考えてみよう外しても進めます
出品したデータを、画面を閉じても消えないようにしたいとき、次に進むのはどちらですか。
選ぶと、この枚の中で答えと理由を確かめられます。
3 / 5
React Routerとアプリ設計 - 完成と次のステップ
1つだけ残った偽物
本物ではないところが、まだ1つ残っています。
画面を閉じると、出品したものは消えてしまいます。
// mock-api.json はブラウザの中だけの偽物4 / 5
作りかけを本物にする作業は、初めて作る作業より達成感が大きい
62 レッスンかけて、自分の手で動くアプリに仕上げました。
5 / 5
