FlatListパフォーマンス改善の秘訣:initialNumToRender以外の最適化手法
initialNumToRender
とは何か?
このプロパティは、FlatList
が最初にマウントされたときに、画面上に表示する(または、表示されうる)アイテムの数を指定します。つまり、ユーザーがリストをスクロールする前に、どのくらいの数のアイテムを事前にレンダリングしておくかを決めるものです。
なぜこれが重要なのか?
-
パフォーマンスの向上:
FlatList
は大量のデータを扱う際に非常に効率的ですが、すべてのアイテムを一度にレンダリングしようとすると、特にアイテム数が多い場合や各アイテムが複雑なコンポーネントである場合に、アプリケーションの起動時やリストの表示時にパフォーマンスの問題(カクつきや遅延)が発生する可能性があります。initialNumToRender
を適切に設定することで、最初に必要最小限のアイテムのみをレンダリングし、それ以外のアイテムはユーザーがスクロールして表示領域に入ったときに「遅延ロード」されます。これにより、初期ロード時間を短縮し、よりスムーズなユーザー体験を提供できます。
-
メモリ使用量の削減:
- 一度にレンダリングされるコンポーネントの数を制限することで、アプリケーションが使用するメモリ量を削減できます。これは、リソースが限られているモバイルデバイスにおいては特に重要です。
どのように設定するのか?
initialNumToRender
は FlatList
コンポーネントのプロパティとして数値で指定します。
<FlatList
data={myData}
renderItem={({ item }) => <MyListItem item={item} />}
keyExtractor={item => item.id}
initialNumToRender={10} // 例: 最初の10個のアイテムをレンダリングする
/>
適切な値の選び方
initialNumToRender
の最適な値は、アプリケーションの要件やリストの表示方法によって異なります。
- テストと調整: 実際に様々なデバイスでテストを行い、アプリケーションのパフォーマンスを監視しながら最適な値を見つけることが重要です。
- 「空白」の回避: 値が小さすぎると、ユーザーが少しスクロールしただけで、まだレンダリングされていない「空白」が表示されてしまう可能性があります。これを避けるためには、画面に表示されるアイテム数よりも少し多めに設定することを検討してください。
- 画面に収まるアイテム数: ユーザーがスクロールせずに一度に画面に表示されるであろうアイテムの数を基準に設定するのが一般的です。例えば、スマートフォンで5〜7個のアイテムが表示される場合、
initialNumToRender={7}
やinitialNumToRender={10}
といった値を試すことができます。
- この値を非常に大きく設定すると、パフォーマンス向上のメリットが失われ、初期ロード時間が長くなる可能性があります。
initialNumToRender
は、リストの最初の表示にのみ影響します。ユーザーがスクロールすると、FlatList
はwindowSize
などの他のプロパティに基づいてアイテムのレンダリングとアンマウントを動的に管理します。
FlatList#initialNumToRender
のよくあるエラーとトラブルシューティング
initialNumToRender
は、リストが最初にレンダリングされるアイテム数を制御することで、初期ロードのパフォーマンスを向上させるためのものです。しかし、このプロパティだけが FlatList のパフォーマンスを決定するわけではなく、他の多くの要因が絡み合っています。
空白領域の表示 (Blank Areas / White Screen)
問題:
initialNumToRender
の値が小さすぎる場合、ユーザーがリストを少しスクロールしただけで、まだコンテンツがロードされていない空白の領域(白い画面)が表示されてしまうことがあります。特に、デバイスの画面サイズが大きく、一度に多くのアイテムが表示される場合に顕著です。
原因:
initialNumToRender
で指定された数以上のアイテムが画面に表示されようとしているにも関わらず、まだレンダリングが追いついていないためです。
トラブルシューティング:
maxToRenderPerBatch
の調整: これはinitialNumToRender
の後に、スクロール時に一度にレンダリングするアイテムのバッチ数を制御します。この値を増やすことで、スクロール時の空白を減らせますが、JavaScript スレッドの処理負荷が増え、応答性が低下する可能性があります。windowSize
の調整:FlatList
はwindowSize
プロパティに基づいて、表示されているアイテムの上下にどれくらいの範囲のアイテムをレンダリングしておくかを決定します。デフォルトは21(表示領域の上下にそれぞれ10画面分ずつ、合計21画面分)ですが、これを増やすことで空白領域の発生を減らせます。ただし、メモリ使用量が増加する可能性があります。initialNumToRender
の値を増やす: 画面に表示されるアイテム数を十分にカバーできるような値に設定します。例えば、1画面に7つのアイテムが表示されるなら、initialNumToRender={10}
程度に設定すると良いでしょう。
初期ロードの遅延 (Slow Initial Render)
問題:
initialNumToRender
の値を大きくしすぎると、アプリケーションの起動時やリストが最初に表示されるときに、かえってロードに時間がかかり、UI が固まる(Jank)ことがあります。
原因:
initialNumToRender
は、初期表示時に同時にレンダリングされるアイテムの数を指定します。値が大きすぎると、その分多くのコンポーネントを一度にレンダリングしようとするため、JavaScript スレッドがブロックされ、UI がフリーズする原因となります。
トラブルシューティング:
renderItem
コンポーネントの最適化:FlatList
の各アイテム(renderItem
でレンダリングされるコンポーネント)が重い処理を行っていないか確認します。- 複雑なロジックを避ける:
renderItem
内で複雑な計算や多くの状態管理を行わないようにします。 - 画像を最適化する:
renderItem
内に表示される画像は、サイズを最適化したり、react-native-fast-image
のようなキャッシュ対応ライブラリを使用したりすることを検討します。 React.memo
/PureComponent
の活用:renderItem
でレンダリングされるアイテムコンポーネントをReact.memo
でラップしたり、クラスコンポーネントの場合はPureComponent
を継承したりして、不要な再レンダリングを防ぎます。特にrenderItem
関数自体をuseCallback
でメモ化することも重要です。- インライン関数の回避:
renderItem
やkeyExtractor
のプロパティに直接アロー関数を渡すのではなく、コンポーネントの外部で定義した関数を参照するようにします。これにより、不要な関数オブジェクトの再生成を防ぎます。
- 複雑なロジックを避ける:
initialNumToRender
の値を最適化する: 画面に収まる必要最小限のアイテム数に設定し、それ以上増やさないようにします。実際のデバイスでテストし、パフォーマンスモニタリングツール(Flipperなど)を使用して、最適な値を見つけることが重要です。
データが initialNumToRender 以降レンダリングされない (Data Not Loading Beyond Initial Num)
問題:
initialNumToRender
で指定した数までしかアイテムが表示されず、それ以降のアイテムがスクロールしても表示されない、または空白のままになることがあります。
原因:
これは initialNumToRender
単体の問題というよりは、FlatList
の仮想化機能やデータ管理に関する他の問題と複合して発生することが多いです。
InteractionManager
が利用可能でない(例えば、バックグラウンドからの復帰時など)。keyExtractor
が正しく設定されていない。data
プロパティの更新がFlatList
に認識されていない。
トラブルシューティング:
data
プロパティの空配列問題: リストの初期状態が空の配列で、後からデータが追加される場合、初期レンダリングが期待通りにいかないことがあります。この場合は、initialNumToRender
の値を調整するだけでなく、データのロードが完了してからFlatList
をレンダリングするなどの工夫が必要になることもあります。InteractionManager
の理解:FlatList
はInteractionManager
を使用して、アニメーションなどの優先度の高い UI 処理が終わった後に、画面外のアイテムのレンダリングをスケジュールすることがあります。もしInteractionManager
が何らかの理由でブロックされていると、追加のアイテムがレンダリングされない可能性があります。ただし、これは通常、React Native 自体の問題であることが多いため、アプリケーションコードで直接操作することは稀です。extraData
プロパティの使用:FlatList
はPureComponent
であるため、data
プロパティが参照レベルで変更されない限り、再レンダリングされません。data
の中身は変わっているが参照は変わっていない場合(例えば、配列内のオブジェクトの一部が変更されただけの場合)に、FlatList
を強制的に再レンダリングさせるためにextraData
プロパティを使用します。const [selectedId, setSelectedId] = useState(null); // 例: 選択状態を管理 <FlatList data={myData} renderItem={...} keyExtractor={...} extraData={selectedId} // 選択状態が変更されたらFlatListも再レンダリングする />
keyExtractor
の確認: 各アイテムに一意のkey
が割り当てられていることを確認してください。keyExtractor
プロパティが正しく実装されていないと、FlatList
がアイテムを正しく追跡できず、レンダリングの問題を引き起こすことがあります。<FlatList data={myData} renderItem={...} keyExtractor={(item) => item.id.toString()} // 各アイテムに一意のIDがあることを確認 />
スクロールパフォーマンスの低下 (Poor Scrolling Performance)
問題:
リストのスクロールがカクついたり、スムーズでなかったりします。initialNumToRender
の設定自体が直接的な原因であることは少ないですが、他の最適化が不足している場合に顕著になります。
原因:
initialNumToRender
は初期ロードに焦点を当てますが、スクロール中のパフォーマンスは FlatList
の仮想化メカニズム全体に依存します。
- 画像などのメディア要素が最適化されていない。
windowSize
の値が不適切。getItemLayout
が利用されていない。renderItem
コンポーネントが重すぎる。
トラブルシューティング:
disableVirtualization={true}
を避ける: このプロパティは仮想化を無効にし、すべてのアイテムを一度にレンダリングするため、リストのアイテム数が少ない場合にのみ使用すべきです。大量のデータを扱うリストでこれを使用すると、パフォーマンスが著しく低下し、メモリを大量に消費します。decelerationRate="fast"
の設定: スクロールの減速レートを調整することで、一部のデバイスでよりスムーズなスクロール体験を提供できる場合があります。removeClippedSubviews={true}
の使用: このプロパティをtrue
に設定すると、画面外のビューをアンマウントし、メモリ使用量を削減できます。ただし、複雑な UI やアニメーションを使用している場合、予期せぬ動作を引き起こす可能性もあるため、注意が必要です。renderItem
の徹底的な最適化: 上記の「初期ロードの遅延」のトラブルシューティングで述べたように、renderItem
でレンダリングされる各アイテムコンポーネントは可能な限り軽量であるべきです。getItemLayout
の実装: もしすべてのリストアイテムの高さ(または横方向リストの場合は幅)が固定されている場合、getItemLayout
プロパティを実装することで、FlatList
は各アイテムのサイズを非同期で計算する必要がなくなり、大幅なパフォーマンス向上に繋がります。これは最も効果的な最適化の一つです。const ITEM_HEIGHT = 100; // アイテムの固定の高さ <FlatList data={myData} renderItem={...} keyExtractor={...} getItemLayout={(data, index) => ({ length: ITEM_HEIGHT, offset: ITEM_HEIGHT * index, index, })} />
- 最新バージョンの React Native を使用する: React Native の開発チームは常に
FlatList
のパフォーマンス改善に取り組んでいます。可能な限り最新の安定版を使用することで、既存のバグ修正や最適化の恩恵を受けられます。 - React DevTools (Flipper) でプロファイリング: Flipper の React Profiler を使用すると、どのコンポーネントが再レンダリングに時間を要しているか、レンダリングのボトルネックはどこにあるかなどを詳細に分析できます。これにより、問題の根本原因を特定しやすくなります。
console.log
の使用を控える: 開発モードでconsole.log
を大量に使用すると、JavaScript スレッドのパフォーマンスに悪影響を与えます。プロダクションビルドでは削除されるように設定するか、必要なデバッグ情報のみに限定してください。- 開発モードとリリースモードの違い: 開発モード (
dev=true
) では、デバッグ情報や警告の表示のために多くの処理がバックグラウンドで行われるため、パフォーマンスが低下します。必ずリリースモード (release
ビルド) でパフォーマンスをテストしてください。
initialNumToRender
は FlatList
の初期表示パフォーマンスを最適化するためのプロパティです。ここでは、基本的な使い方から、関連するパフォーマンス最適化の例までをコードとともに解説します。
基本的な使い方
最もシンプルな FlatList
の実装で initialNumToRender
を設定する例です。
import React from 'react';
import { View, Text, FlatList, StyleSheet, Dimensions } from 'react-native';
// サンプルデータ
const DATA = Array.from({ length: 1000 }, (_, i) => ({
id: String(i),
title: `アイテム ${i + 1}`,
}));
// リストの各アイテムのコンポーネント
const Item = ({ title }) => (
<View style={styles.item}>
<Text style={styles.title}>{title}</Text>
</View>
);
const BasicFlatListExample = () => {
return (
<View style={styles.container}>
<FlatList
data={DATA}
renderItem={({ item }) => <Item title={item.title} />}
keyExtractor={item => item.id}
initialNumToRender={10} // ここで最初の10個のアイテムをレンダリングする
ListHeaderComponent={<Text style={styles.header}>基本的なFlatListの例</Text>}
/>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
marginTop: 50, // ステータスバーとの重なりを避けるため
},
item: {
backgroundColor: '#f9c2ff',
padding: 20,
marginVertical: 8,
marginHorizontal: 16,
borderRadius: 8,
},
title: {
fontSize: 20,
},
header: {
fontSize: 24,
fontWeight: 'bold',
textAlign: 'center',
marginVertical: 10,
},
});
export default BasicFlatListExample;
解説:
- これにより、大量のデータを持つリストでも、初期表示時のアプリケーションの起動時間を短縮し、よりスムーズなUI体験を提供できます。
initialNumToRender={10}
: この設定により、FlatList
は最初にマウントされたときに、DATA
の中から最初の10個のアイテムだけをレンダリングします。残りのアイテムは、ユーザーがスクロールして表示領域に近づいたときに「遅延ロード」されます。
initialNumToRender と React.memo (または PureComponent) を組み合わせた例
initialNumToRender
は初期ロードに役立ちますが、スクロール中のパフォーマンスをさらに向上させるには、各アイテムコンポーネントの不要な再レンダリングを防ぐことが重要です。React.memo
を使用するのが一般的な方法です。
import React from 'react';
import { View, Text, FlatList, StyleSheet, Dimensions, TouchableOpacity } from 'react-native';
const DATA = Array.from({ length: 1000 }, (_, i) => ({
id: String(i),
title: `メモ化されたアイテム ${i + 1}`,
}));
// ItemコンポーネントをReact.memoでラップ
// propsが変更されない限り、再レンダリングされない
const MemoizedItem = React.memo(({ title, isSelected, onPress }) => {
console.log(`Rendering Item: ${title}`); // レンダリング頻度を確認
return (
<TouchableOpacity onPress={onPress} style={[styles.item, isSelected && styles.selectedItem]}>
<Text style={styles.title}>{title}</Text>
</TouchableOpacity>
);
});
const MemoizedFlatListExample = () => {
const [selectedId, setSelectedId] = React.useState(null);
// renderItem関数もuseCallbackでメモ化するとさらに良い
// FlatListが再レンダリングされる際に、この関数が再生成されないようにする
const renderItem = React.useCallback(({ item }) => (
<MemoizedItem
title={item.title}
isSelected={item.id === selectedId}
onPress={() => setSelectedId(item.id)}
/>
), [selectedId]); // selectedIdが変更されたときのみ再生成
return (
<View style={styles.container}>
<FlatList
data={DATA}
renderItem={renderItem}
keyExtractor={item => item.id}
initialNumToRender={10} // 初期表示のアイテム数
windowSize={21} // 表示されている領域の上下にどれくらいアイテムをレンダリングしておくか(デフォルト値)
maxToRenderPerBatch={5} // initialNumToRender以降、スクロール時に一度にレンダリングするアイテムのバッチ数
// extraDataを渡すことで、selectedIdが変更されたときにFlatList全体を再レンダリングさせる
// これがないと、MemoizedItemはisSelectedの変更を検知できない
extraData={selectedId}
ListHeaderComponent={<Text style={styles.header}>Memoized ItemとFlatListの例</Text>}
/>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
marginTop: 50,
},
item: {
backgroundColor: '#e0f7fa',
padding: 20,
marginVertical: 8,
marginHorizontal: 16,
borderRadius: 8,
},
selectedItem: {
backgroundColor: '#00bcd4',
},
title: {
fontSize: 20,
},
header: {
fontSize: 24,
fontWeight: 'bold',
textAlign: 'center',
marginVertical: 10,
},
});
export default MemoizedFlatListExample;
解説:
extraData={selectedId}
:FlatList
は内部的にPureComponent
のような挙動をするため、data
プロパティの参照が変わらない限り、FlatList
は再レンダリングされません。しかし、この例ではselectedId
が変更されると、一部のアイテムのisSelected
プロパティが変更される必要があります。extraData
にselectedId
を渡すことで、selectedId
が変更されたときにFlatList
に再レンダリングを促し、関連するMemoizedItem
が適切に更新されるようにします。renderItem = React.useCallback(...)
:renderItem
関数自体をuseCallback
でメモ化しています。これにより、MemoizedFlatListExample
コンポーネントが再レンダリングされても、renderItem
関数が再生成されることを防ぎ、FlatList
の不要な再レンダリングを避けることができます。MemoizedItem = React.memo(...)
: 各アイテムコンポーネントをReact.memo
でラップすることで、MemoizedItem
に渡されるprops
が変更されない限り、そのアイテムは再レンダリングされません。これにより、スクロール時の不要な再レンダリングが大幅に減り、パフォーマンスが向上します。console.log
を見ると、選択されたアイテムとその周辺のアイテムのみが再レンダリングされていることがわかります。
initialNumToRender と getItemLayout を組み合わせた例 (固定高さのアイテム)
リスト内のすべてのアイテムの高さが固定されている場合、getItemLayout
プロパティを実装することで、FlatList
のスクロールパフォーマンスを劇的に向上させることができます。これは initialNumToRender
の設定とは独立して重要です。
import React from 'react';
import { View, Text, FlatList, StyleSheet, Dimensions } from 'react-native';
const DATA = Array.from({ length: 1000 }, (_, i) => ({
id: String(i),
title: `固定高さのアイテム ${i + 1}`,
}));
const ITEM_HEIGHT = 80; // 各アイテムの固定の高さ
const FixedHeightItem = ({ title }) => (
<View style={styles.fixedItem}>
<Text style={styles.title}>{title}</Text>
</View>
);
const GetItemLayoutFlatListExample = () => {
return (
<View style={styles.container}>
<FlatList
data={DATA}
renderItem={({ item }) => <FixedHeightItem title={item.title} />}
keyExtractor={item => item.id}
initialNumToRender={10} // 初期表示のアイテム数
// ここが重要:getItemLayoutの実装
getItemLayout={(data, index) => (
{ length: ITEM_HEIGHT, offset: ITEM_HEIGHT * index, index }
)}
ListHeaderComponent={<Text style={styles.header}>getItemLayoutとFlatListの例</Text>}
/>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
marginTop: 50,
},
fixedItem: {
backgroundColor: '#c8e6c9',
height: ITEM_HEIGHT, // 高さを固定
justifyContent: 'center',
padding: 20,
marginVertical: 8,
marginHorizontal: 16,
borderRadius: 8,
},
title: {
fontSize: 20,
},
header: {
fontSize: 24,
fontWeight: 'bold',
textAlign: 'center',
marginVertical: 10,
},
});
export default GetItemLayoutFlatListExample;
解説:
- 注意: アイテムの高さが動的に変わる場合、
getItemLayout
は使用できません。その場合は、React.memo
やwindowSize
などの他の最適化に頼ることになります。 getItemLayout
を実装すると、FlatList
は各アイテムのサイズと位置を事前に計算できるようになります。これにより、スクロール時のレイアウト計算のオーバーヘッドがなくなり、非常にスムーズなスクロールが可能になります。getItemLayout={(data, index) => ({ length: ITEM_HEIGHT, offset: ITEM_HEIGHT * index, index })}
:length
: 各アイテムの長さを指定します(この場合は高さ)。offset
: リストの先頭からのアイテムのオフセット(位置)を指定します。index
: アイテムのインデックスです。
ITEM_HEIGHT = 80
: 各アイテムの固定の高さを定義します。
FlatList#initialNumToRender
は、リストの初期ロードパフォーマンスを改善する素晴らしいツールですが、最高のパフォーマンスを得るためには、以下の点を考慮してコードを最適化することが重要です。
initialNumToRender
の適切な値: 画面に表示されるアイテム数より少し多めに設定し、空白表示を避けつつ、初期ロードの遅延を防ぎます。renderItem
コンポーネントの最適化:- 軽量化: アイテムコンポーネント内のロジックや描画をできるだけシンプルにします。
React.memo
の使用:props
が変更されない限り再レンダリングしないように、renderItem
でレンダリングされるコンポーネントをReact.memo
でラップします。useCallback
の使用:renderItem
関数自体をuseCallback
でメモ化し、親コンポーネントの再レンダリングによる不要な関数再生成を防ぎます。
keyExtractor
の適切な実装: 各アイテムに一意のキーを割り当て、FlatList
がアイテムを効率的に追跡できるようにします。getItemLayout
の活用: アイテムの高さが固定されている場合は、必ずgetItemLayout
を実装します。これはパフォーマンスに最も大きな影響を与える最適化の一つです。extraData
の使用:data
プロパティの参照が変わらないが、アイテム内部のデータが変更された場合に、FlatList
に再レンダリングを促します。windowSize
とmaxToRenderPerBatch
の調整: 必要に応じてこれらのプロパティを調整し、スクロール中の空白領域とメモリ使用量のバランスを取ります。
initialNumToRender
は FlatList
が最初に描画するアイテム数を制御しますが、リストのパフォーマンス全体は、初期レンダリングだけでなく、スクロール中のパフォーマンス、メモリ使用量、データ取得方法など、様々な要因によって決まります。ここでは、initialNumToRender
と組み合わせて使用することで効果を高める方法や、異なるアプローチを紹介します。
windowSize の調整
windowSize
は FlatList
が現在の表示領域(viewport)の上下にどれくらいのアイテムをレンダリングしておくかを決定するプロパティです。これは initialNumToRender
と密接に関連しており、特にスクロール中のパフォーマンスに影響します。
- 調整の目的:
windowSize
を増やすと、ユーザーが素早くスクロールしたときに空白領域が表示されるのを防ぐことができますが、同時にメモリ使用量が増加し、レンダリング負荷も増えます。逆に減らすとメモリ使用量は減りますが、空白領域が表示されやすくなります。 - デフォルト値: デフォルトは
21
です(ビューポートの上下にそれぞれ10画面分ずつ、合計21画面分)。
コード例:
initialNumToRender
と同様に FlatList
のプロパティとして設定します。
<FlatList
data={myData}
renderItem={({ item }) => <MyListItem item={item} />}
keyExtractor={item => item.id}
initialNumToRender={10} // 例: 初期は10個
windowSize={11} // 例: 画面上下に5画面分ずつ(合計11画面分)
/>
解説:
initialNumToRender
は「最初の表示」に特化していますが、windowSize
は「スクロール中のレンダリングバッファ」と考えると良いでしょう。両方を適切に設定することで、初期ロードとスクロールパフォーマンスの両方を最適化できます。
maxToRenderPerBatch の調整
これは initialNumToRender
の後に、スクロール時に一度にレンダリングするアイテムのバッチ数を制御するプロパティです。
- 注意点: 値を大きくしすぎると、一度に多くの処理が走り、JavaScript スレッドがブロックされ、UI がカクつく可能性があります。
- 目的: スクロール中に一度に多くのアイテムをレンダリングすることで、空白領域の表示を減らすことができます。
コード例:
<FlatList
data={myData}
renderItem={({ item }) => <MyListItem item={item} />}
keyExtractor={item => item.id}
initialNumToRender={10}
maxToRenderPerBatch={5} // デフォルトは10ですが、必要に応じて調整
/>
解説:
maxToRenderPerBatch
は、initialNumToRender
で表示された後、ユーザーがスクロールするたびに、追加でどれだけのアイテムをレンダリングするかを制御します。これもまた、空白領域の回避とパフォーマンスのバランスを取るための重要なプロパティです。
getItemLayout の活用 (最も効果的な最適化の一つ)
もしリスト内のすべてのアイテムの高さ(または幅)が固定されている場合、getItemLayout
を実装することは、FlatList
のスクロールパフォーマンスを劇的に向上させる最も効果的な方法です。これは initialNumToRender
の代替というよりは、強力な補完です。
- なぜ効果的か:
FlatList
は各アイテムのサイズとオフセットを非同期で計算しますが、getItemLayout
を提供することで、この計算をスキップし、正確な位置を瞬時に決定できるようになります。これにより、仮想化が非常に効率的に機能し、スクロールが非常にスムーズになります。
コード例:
const ITEM_HEIGHT = 100; // 各アイテムの固定の高さ
<FlatList
data={myData}
renderItem={({ item }) => <MyListItem item={item} />}
keyExtractor={item => item.id}
initialNumToRender={10}
getItemLayout={(data, index) => (
{ length: ITEM_HEIGHT, offset: ITEM_HEIGHT * index, index }
)}
/>
解説:
initialNumToRender
が初期ロードを助けるのに対し、getItemLayout
はリスト全体のレンダリングとスクロールの効率性を高めます。アイテムの高さが固定されている場合は、必ずこのプロパティを使用することを強くお勧めします。
renderItem コンポーネントの徹底的な最適化
initialNumToRender
や他のプロパティ設定も重要ですが、個々のリストアイテム(renderItem
で描画されるコンポーネント)が重いと、どんな最適化も効果が薄れてしまいます。これは initialNumToRender
の直接的な代替ではありませんが、パフォーマンス改善の最も重要な基礎です。
- インライン関数の回避:
renderItem
やkeyExtractor
のプロパティに直接アロー関数を渡すのではなく、コンポーネントの外部で定義した関数を参照するようにします。
- 軽量なアイテムコンポーネント:
- アイテム内で複雑な計算や多くの状態管理を行わない。
- 画像の最適化(サイズ、フォーマット、
react-native-fast-image
のようなライブラリの使用)。 - 可能な限りシンプルなビュー構造にする。
useCallback
でrenderItem
をメモ化:- 親コンポーネントが再レンダリングされる際に、
renderItem
関数が再生成されることを防ぎ、FlatList
の不要な再レンダリングを避けます。
- 親コンポーネントが再レンダリングされる際に、
React.memo
(またはPureComponent
) の使用:- アイテムコンポーネントを
React.memo
でラップすることで、props
が変更されない限り不要な再レンダリングを防ぎます。
- アイテムコンポーネントを
コード例: (上記「プログラミング例」の2.で詳細に解説済み)
// アイテムコンポーネントをメモ化
const MemoizedItem = React.memo(({ title, isSelected, onPress }) => { /* ... */ });
// renderItem関数もメモ化
const renderItem = React.useCallback(({ item }) => (
<MemoizedItem
title={item.title}
isSelected={item.id === selectedId}
onPress={() => setSelectedId(item.id)}
/>
), [selectedId]); // 依存配列に注意
<FlatList
data={DATA}
renderItem={renderItem} // メモ化した関数を参照
keyExtractor={item => item.id}
initialNumToRender={10}
extraData={selectedId} // 関連するstateを伝える
/>
removeClippedSubviews の使用
このプロパティを true
に設定すると、画面外のビューをアンマウントし、メモリ使用量を削減することができます。
- 注意点: 複雑な UI やアニメーションを使用している場合、予期せぬ動作や視覚的な不具合を引き起こす可能性があるため、慎重に使用し、十分にテストする必要があります。
- 目的: 特に多数のアイテムを持つリストでメモリ使用量を抑えたい場合に有効です。
コード例:
<FlatList
data={myData}
renderItem={({ item }) => <MyListItem item={item} />}
keyExtractor={item => item.id}
initialNumToRender={10}
removeClippedSubviews={true} // 画面外のビューをアンマウントする
/>
仮想化が不要な場合の代替 (ScrollView)
もしリストのアイテム数が非常に少なく(例えば20個以下)、かつすべてのアイテムが一度に画面に表示されるような場合、あるいは仮想化による問題(例えば複雑なヘッダーやフッターのレイアウト)を避けたい場合は、FlatList
の代わりにシンプルな ScrollView
を使用することも検討できます。
ScrollView
の欠点:- 大量のデータには不向き。すべてのアイテムが一度にレンダリングされるため、メモリを大量に消費し、パフォーマンスが著しく低下する。
ScrollView
の利点:- セットアップが簡単。
- 仮想化の概念がないため、
initialNumToRender
やwindowSize
のようなプロパティを気にする必要がない。 - すべてのコンテンツが一度にレンダリングされるため、動的な高さのコンテンツや複雑なネストされたビューとの相性が良い場合がある。
コード例:
import React from 'react';
import { View, Text, ScrollView, StyleSheet } from 'react-native';
const DATA = Array.from({ length: 15 }, (_, i) => ({ // 少ないデータ
id: String(i),
title: `スクロールビューアイテム ${i + 1}`,
}));
const Item = ({ title }) => (
<View style={styles.item}>
<Text style={styles.title}>{title}</Text>
</View>
);
const ScrollViewExample = () => {
return (
<ScrollView style={styles.container}>
<Text style={styles.header}>ScrollViewの例</Text>
{DATA.map(item => (
<Item key={item.id} title={item.title} />
))}
</ScrollView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
marginTop: 50,
},
item: {
backgroundColor: '#ffccbc',
padding: 20,
marginVertical: 8,
marginHorizontal: 16,
borderRadius: 8,
},
title: {
fontSize: 20,
},
header: {
fontSize: 24,
fontWeight: 'bold',
textAlign: 'center',
marginVertical: 10,
},
});
export default ScrollViewExample;
解説:
ScrollView
は、アイテム数が少ない場合に、FlatList
の仮想化による複雑さを避けたい場合の代替となりえます。ただし、パフォーマンス要件が厳しい場合やデータ量が多い場合は、FlatList
の使用と上記の最適化が必須となります。
FlatList#initialNumToRender
は初期ロードの最適化に役立ちますが、リスト全体のパフォーマンスを最大化するためには、以下の代替・補完的な手法を総合的に考慮することが重要です。
ScrollView
: 少ないアイテム数の場合のシンプルな代替手段。removeClippedSubviews
: メモリ使用量の削減。renderItem
コンポーネントの徹底的な最適化:React.memo
やuseCallback
を活用し、各アイテムのレンダリングコストを最小限に抑える。getItemLayout
: 固定高さのアイテムにおける最も効果的なスクロールパフォーマンス向上策。windowSize
とmaxToRenderPerBatch
: スクロール中のバッファリングとバッチレンダリングの制御。