レンダリング単位として適切でない構造(List内の重いItemなど)
はじめに
リスト構造はReactでもっともよく使われるUIパターンですが、そこに“重すぎるItem”が紛れていると、アプリ全体の体感速度や構造の読みやすさに深刻な影響を与えることがあります。
この記事では、リスト内コンポーネントが抱えてはいけない責務とは何か、レビューアーの視点でどこに注目し、どのような改善を提案すべきかを整理していきます。
「重いItem」とは何か?
重いItemとは
描画単位としてのリストアイテムが、状態管理・副作用・非同期処理・ロジックなど本来親コンポーネントが持つべき責務を過剰に内包している状態を指します。
よくある“重すぎるItem”の構造
export function UserList({ users }) {
return (
<ul>
{users.map((user) => (
<UserItem key={user.id} userId={user.id} />
))}
</ul>
);
}
export function UserItem({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
fetch(`/api/user/${userId}`)
.then(res => res.json())
.then(setUser);
}, [userId]);
return <li>{user?.name}</li>;
@Reviewer各Item内でAPI呼び出しが発生しており、構造上Itemが重くなっています。親側で一括fetchし、propsで渡す方が構造・パフォーマンスともに安定します。}
- 非同期処理の同時多発(N回fetch)
- メモ化やPureComponentの効果が効かない
- パフォーマンス測定でList部がボトルネックになる
観点:Itemが持ってはいけない責務
責務 | 原則的にどこで持つべきか? |
---|---|
データ取得 | 親コンポーネント or Container |
状態管理 | UIロジックなら親、編集中フラグなどは子 |
非同期副作用 | Custom Hookに分離して親で合成 |
イベントハンドラロジック | 親で定義し、子にはcallbackを渡す |
改善パターン1:親が一括取得して子に渡す
UserList.tsx
const [users, setUsers] = useState([]);
useEffect(() => {
fetch('/api/users')
.then(res => res.json())
.then(setUsers);
}, []);
return (
<ul>
{users.map((u) => (
<UserItem key={u.id} user={u} />
))}
</ul>
);
UserItem.tsx
export function UserItem({ user }) {
return <li>{user.name}</li>;
}
副作用を排除し、描画とデータの責務を分離した構造になります。
改善パターン2:表示コストが高い場合はwindowing
import { FixedSizeList as List } from 'react-window';
<List
height={400}
itemCount={users.length}
itemSize={50}
width="100%"
>
{({ index, style }) => (
<div style={style}>
<UserItem user={users[index]} />
</div>
)}
</List>
大量描画が必要な場合は、表示範囲だけを描画するvirtual list構造を使うことでパフォーマンスが向上します。
構造の違いを図示
後者では非同期処理が親に集約されており、構造的に再利用やテストがしやすくなっています。
レビュー時のコメントテンプレート
非同期処理の責務混在
@Reviewer: 各リストItemでAPI呼び出しが発生しており、構造的にItemが重くなっています。データ取得は親側に集約し、propsで渡す構造にすると、責務が明確になります。
改善提案
- <UserItem userId={user.id} />
+ <UserItem user={user} />
まとめ
Reactでパフォーマンスや構造に悩まされるとき、実は「List内のItemが重すぎる」というケースは非常に多いです。
レビューアーは以下のような問いを持ちながら、構造をチェックすると良いでしょう:
- Itemが副作用や状態を抱えすぎていないか?
- その責務は親で担えないか?
- 表示件数が多い場合の対策は取られているか?
責務を正しく分離し、リストItemを「描画単位」として適切な重さに保つことが、Reactアプリの安定性と可読性を支える鍵になります。