リストの件数が増えた途端、スクロールがカクつく・初期表示が固まる。React Nativeのリスト表示で必ず一度はぶつかる壁です。
この記事でわかることFlatList が重くなる原因を切り分け、効く順に最適化していく手順をコードつきで書きます。「とりあえず全部盛り」ではなく、何がどう効くかを理解して直せるようにします。
- 結論:効く順に4つ直す
- 大前提:ScrollView+mapをやめる
- keyExtractorと行のmemo化
- getItemLayoutで描画を先読みさせる
- レンダリング件数のチューニング
- まとめ
FlatListの最適化は順番が大事です。効果が大きい順に、①ScrollView+mapをFlatListにする ②行をmemo化する ③getItemLayoutを渡す ④描画件数を調整する。上から順に試して、改善したらそこで止めるのが正しいやり方です。
一番多い原因がこれです。ScrollView の中で map すると、画面外も含めて全アイテムが一度に生成される。100件あれば100件分のViewが即座に作られ、初期表示で固まります。
// ❌ 悪い例:全件を一度に描画する
<ScrollView>
{items.map((item) => (
<PostItem key={item.id} post={item} />
))}
</ScrollView>
// ✅ 良い例:FlatListは画面に見える分だけ描画する
<FlatList
data={items}
keyExtractor={(item) => item.id}
renderItem={({ item }) => <PostItem post={item} />}
/>
? POINT
FlatListは「仮想化リスト」です。画面に表示される範囲+少しだけを描画し、スクロールに応じて使い回します。これが効くからこそ、ScrollView+mapより圧倒的に速くなります。keyExtractor で安定したキーを渡すこと。indexをキーにすると並び替えや追加でReactが余計な再描画をします。さらに行コンポーネントを React.memo で包むと、関係ない再レンダリングを止められます。
import { memo } from 'react';
// 行コンポーネントをmemo化
const PostItem = memo(function PostItem({ post }: { post: Post }) {
return (
<View style={styles.row}>
<Text>{post.title}</Text>
</View>
);
});
注意点として、renderItem にインラインで重い関数やオブジェクトを毎回作るとmemoが効きません。onPress も useCallback で固定します。
const handlePress = useCallback((id: string) => {
navigation.navigate('Detail', { id });
}, [navigation]);
// renderItemは外で定義 or useCallbackで固定
const renderItem = useCallback(
({ item }: { item: Post }) => (
<PostItem post={item} onPress={handlePress} />
),
[handlePress]
);
行の高さが固定なら getItemLayout を渡します。各行の位置を測らずに計算で求められるため、スクロールが滑らかになり、特定位置へのジャンプも速くなります。
const ROW_HEIGHT = 72;
<FlatList
data={items}
keyExtractor={(item) => item.id}
renderItem={renderItem}
getItemLayout={(_, index) => ({
length: ROW_HEIGHT,
offset: ROW_HEIGHT * index,
index,
})}
/>
⚠️ 注意
getItemLayout は高さが全行で一定のときだけ使えます。可変高さの行に固定値を渡すとスクロール位置がズレるので使わないでください。
最後に、それでも重いときだけ描画件数を調整します。やみくもに全部いじらず、初期表示が遅いなら initialNumToRender、スクロール時のカクつきなら windowSize と maxToRenderPerBatch を触ります。
<FlatList
data={items}
keyExtractor={(item) => item.id}
renderItem={renderItem}
initialNumToRender={8} // 初期表示する件数
maxToRenderPerBatch={8} // 1バッチで追加描画する件数
windowSize={5} // 画面の何倍を保持するか
removeClippedSubviews // 画面外Viewを切り離す(Android向け)
/>
- まずScrollView+mapをFlatListに置き換える(効果が一番大きい)
- keyExtractorは安定キー、行はReact.memoで包む
- 高さ固定ならgetItemLayoutで先読みさせる
- 件数チューニングは最後の手段、症状に応じて該当プロパティだけ触る


コメント