見た目と機能の分離

1つの部品が2つの仕事を持っている

いまの ItemList は、次の2つを1つの関数の中でやっています。

  • データを取ってくる仕事 (fetch と state と読み込み中の管理)
  • それを画面に並べる仕事 (JSX とクラス名)

動いてはいますが、困りごとが2つあります。まず、見た目だけを直したいときにも通信のコードを読むことになります。そして、同じ並べ方を「お気に入り一覧」でも使いたくなったとき、取得先が違うだけなのに JSX ごと複製することになります。

表示担当とデータ担当に分ける

そこで役割で2つに割ります。呼び方は次のとおりです。

  • 表示担当 (Presentational) — props で受け取ったものを並べるだけ。state も通信も持たない
  • データ担当 (Container) — 取ってきて、状態を持って、表示担当に渡すだけ。JSX はほぼ書かない
type ItemListViewProps = { items: Item[]; }; export function ItemListView({ items }: ItemListViewProps) { return ( <div className="list"> {items.map((item) => ( <div className="card" key={item.id}> <p className="title">{item.title}</p> <p className="price">{item.price}</p> </div> ))} </div> ); }

表示担当は props だけを見ます。これが守れていると、渡す配列を変えるだけでどんな画面も再現できます。

データ担当は渡すだけにする

export function ItemList() { const [items, setItems] = useState<Item[]>([]); useEffect(() => { fetch("/api/items") .then((res) => res.json()) .then((data: Item[]) => setItems(data)); }, []); return <ItemListView items={items} />; }

分かれ目は覚えやすい形になっています。表示担当に useState と fetch が出てきたら、分け方を間違えています。

演習

いまの ItemList を、表示担当の ItemListView とデータ担当の ItemList に分けます。

ヒント

App.tsx
ItemList.tsx
ItemListView.tsx
mock-api.json
types.ts
App.tsx
ItemList.tsx
ItemListView.tsx
mock-api.json
types.ts
プレビュー

できているか

  • カードが3枚並んでいる
  • 3枚とも価格が出ている
  • 帆布トートが出ている
  • 価格に 円 が付いている