お題
- 配列に詰まったアイテムをそれぞれ表示する
- アイテムを表示するコンポーネントには副作用がある
- つまり、不要なre-renderを避けたい
- 今回は単に初期表示時点の`Date.now()`
- アイテムは増減する
という、あるあるな要件を、それぞれのフレームワークだとどう書くことになるか。
端的には、アイテムを増減させても、関係のない各アイテムのコンポーネントはそっとしておいてほしい。
Solid
import { For, createSignal } from "solid-js"; const Item = (props) => ( <div> #{props.item} at {Date.now()} <button onClick={props.delete}>DEL</button> </div> ); const App = () => { const [items, setItems] = createSignal([1]); const addItem = () => setItems((prev) => [...prev, prev.length + 1]); const deleteItem = (idx) => setItems((prev) => [...prev.filter((_, i) => idx !== i)]); return ( <> <button onClick={addItem}>ADD</button> <hr /> <For each={items()}> {(item, idx) => ( <Item item={item} delete={() => deleteItem(idx())} /> )} </For> </> ); };
`For`コンポーネントでループを表現するだけで、不要なre-renderがスキップされるところがポイント。
Svelte
<!-- ================== item.svelte ===================== --> <script> export let item; </script> <div> #{item} at {Date.now()} <button on:click>DEL</button> </div> <!-- ================== app.svelte ===================== --> <script> import Item from "./item.svelte"; let items = [1]; const addItem = () => { items = [...items, items.length + 1]; }; const deleteItem = (idx) => { items = [...items.filter((_, i) => idx !== i)]; }; </script> <button on:click={addItem}>ADD</button> <hr> {#each items as item, idx} <Item {item} on:click={() => deleteItem(idx)} /> {/each}
こちらも`#each`で書けばre-renderされない。`on:click`って書くだけでイベントがdelegateできるのも嬉しい。
(P)React
import { useState, memo } from "react"; const Item = memo( ({ item, delete: del }) => ( <div> #{item} at {Date.now()} <button onClick={del}>DEL</button> </div> ), (a, b) => a.item === b.item ); const App = () => { const [items, setItems] = useState([1]); const addItem = () => setItems((prev) => [...prev, prev.length + 1]); const deleteItem = (idx) => setItems((prev) => [...prev.filter((_, i) => idx !== i)]); return ( <> <button onClick={addItem}>ADD</button> <hr /> {items.map((item, idx) => ( <Item item={item} delete={() => deleteItem(idx)} key={item} /> ))} </> ); };
`memo()`でラップして、どういう条件ならre-renderしなくていいかを指定するのがポイント。
まとめ
- Solid
- JSXなので、`map()`で書くこともでき(その場合はre-renderしちゃう)、そういうところは中途半端
- 必要なところだけ使えばいい、選べて嬉しいという見方もできる
- Svelte
- ループを表現するには`#each`しかなく、それで最適化がなされるのでわかりやすい
- 手数が最も少なくて素敵だが、コンポーネントはかさばる
- (P)React
- 良くも悪くも何もしてくれない
- そういうのは全部手動でやらないといけないので、もっと条件が複雑になったら?みたいな話になる
- Reactの場合は別途`react-dom`税がかかる
Vue派の人はすいません。けどたぶん`v-for`を使ったときはよしなにしてくれるので、Solidに近い体験な気がする。