見た目と機能の分離
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 に分けます。