Skip to content

React Native の長い一覧が重いとき:FlatList を調整する前に測る

一覧のカクつきは FlatList のウィンドウサイズだけが原因とは限らない。行での高コスト計算、画像のデコード、画面全体の再描画も同じ症状になる。まず本番に近いビルドで再現する。

どの処理が重いか確認する ​

端末、件数、画像サイズ、操作手順を記録し、JS と UI スレッドのフレーム時間を比べる。JS 側が忙しければ行の依存状態や描画中の計算を見直す。UI 側なら画像、レイアウト、ネイティブ View を調べる。デバッグビルドだけで結論を出さない。

根拠のある設定だけ変更する ​

行の高さが本当に固定なら getItemLayout で位置計測を省ける。キーには安定した業務 ID を使う。ページング要求は重複させず、検索条件が変わった後に古い応答を反映しない。ウィンドウやバッチの値は低性能端末でメモリと空白表示を測って決める。

tsx
<FlatList
  data={items}
  keyExtractor={item => item.id}
  renderItem={renderItem}
  getItemLayout={(_, index) => ({ length: ROW_HEIGHT, offset: ROW_HEIGHT * index, index })}
  onEndReached={loadNextPage}
/>

翻訳や文字サイズ拡大で行高が変わるなら固定高さの前提は崩れる。高速スクロール、未キャッシュ画像、条件変更、大きな文字で検証する。参考:React Native パフォーマンス。

再現できる引っ掛かりから始める ​

商品一覧の 80 行目付近でスクロールが引っ掛かるとする。同じ端末・同じデータのリリースビルドで再現し、方向、行数、画像サイズ、フレーム落ちの区間を記録する。開発モードの負荷は診断を歪める。次に JS スレッドと UI スレッドを分けて見る。価格の整形や広範囲の状態更新は JS を詰まらせ、画像のデコードや重い描画は JS が空いていても UI を詰まらせる。最初から windowSize を変えると、引っ掛かりを空白セルに置き換えるだけかもしれない。

高さが固定の行には getItemLayout を使える。一方、折り返すタイトル、拡大文字、展開領域のある行に固定高さを偽装すると scrollToIndex がずれる。画像は表示寸法に近いものを読み、キャッシュする。行には表示に必要な安定した値だけを渡す。renderItem の関数や親から渡すグローバル状態が毎回変われば、memo を使っても大量の再描画が起こり得る。

tsx
const Row = memo(function Row({ item, onOpen }: RowProps) {
  return <Pressable onPress={() => onOpen(item.id)}><Text>{item.title}</Text></Pressable>;
});
// data、keyExtractor、onOpen の安定性も確認する。

仮説を一つずつ検証する ​

基準値としてフレーム時間、初回表示時間、メモリ最大値、空白セル数を記録する。描画範囲が疑わしければ仮想化の設定を一つだけ変え、同じスクロールを繰り返す。状態の伝播が疑わしければ各行の描画回数を数え、商品を一つだけお気に入りにして影響範囲を調べる。低性能端末で高速スクロール、位置の復元、文字拡大、画像失敗も確認する。フレーム値が改善しても空白や読み上げフォーカスの喪失があれば完了ではない。

MIT Licensed