React Native FlatListパフォーマンス向上ガイド:getItemLayoutから代替案まで
getItemLayout
とは何か?
FlatList
は、非常に長いリストを効率的に表示するために「仮想化 (Virtualization)」という技術を使用しています。これは、画面に表示されているアイテムのみをレンダリングし、スクロールに応じて不要なアイテムをアンマウントし、新しいアイテムをレンダリングすることでメモリ使用量とパフォーマンスを最適化する仕組みです。
通常、FlatList
は各アイテムのサイズ(高さまたは幅)を測定することで、リスト全体のレイアウトを計算します。しかし、この測定は非同期で行われるため、特にアイテム数が多い場合や、アイテムのサイズが動的に変わる場合に、スクロールがカクついたり、空白が表示されたりするなどのパフォーマンスの問題が発生することがあります。
getItemLayout
プロパティは、この問題に対する最適化策です。このプロパティに関数を渡すことで、FlatList
に各アイテムのサイズと位置を事前に教えることができます。これにより、FlatList
はアイテムの測定をスキップし、事前に計算された情報に基づいてスムーズにスクロールできるようになります。
getItemLayout
のメリット
- 正確なスクロール位置の維持:
scrollToIndex
などのメソッドを使用する際に、より正確なスクロール位置を保証できます。 - スムーズなスクロール: アイテムのレイアウト計算によるカクつきが軽減され、よりスムーズなスクロール体験を提供します。
- パフォーマンスの向上: アイテムの測定が不要になるため、特に長いリストでの初回レンダリングやスクロール時のパフォーマンスが大幅に向上します。
getItemLayout
関数のシグネチャと戻り値
getItemLayout
に渡す関数は、以下のシグネチャを持ちます。
getItemLayout={(data, index) => ({
length: number, // 各アイテムの長さ(高さまたは幅)
offset: number, // そのアイテムがリストの先頭からどれだけ離れているか
index: number, // アイテムのインデックス
})}
index
: 現在処理されているアイテムのインデックスです。data
:FlatList
に渡されたdata
プロパティ(リストのデータ配列)です。
戻り値は以下のプロパティを持つオブジェクトです。
index
: そのアイテムのインデックスです。offset
: そのアイテムがリストの先頭からどれだけ離れているか(ピクセル単位)を指定します。これは通常、length * index
で計算されます。アイテム間に区切り線などがある場合は、その長さも考慮する必要があります。length
: そのアイテムの長さ(垂直方向のリストなら高さ、水平方向のリストなら幅)をピクセル単位で指定します。
getItemLayout
を使用する際の注意点
- 不正確な値の指定: もし
getItemLayout
で不正確な値を返してしまうと、スクロール位置のずれやアイテムの表示がおかしくなるなどの問題が発生する可能性があります。 - 横方向のリストの場合:
horizontal
プロパティをtrue
にしている場合、length
はアイテムの「幅」を、offset
はリストの「左端」からの距離を示します。 - セパレータの考慮:
ItemSeparatorComponent
などでアイテム間にスペースや区切り線がある場合、offset
の計算にその分も考慮する必要があります。- 例:
offset: (ITEM_HEIGHT + SEPARATOR_HEIGHT) * index
- 例:
- アイテムのサイズが固定であること:
getItemLayout
が最も効果を発揮するのは、すべてのリストアイテムのサイズが固定である場合です。アイテムのサイズが動的に変わる場合は、正確なlength
とoffset
を事前に計算することが難しく、かえって問題を引き起こす可能性があります。- もしアイテムの高さが異なる場合でも、各アイテムの正確な高さを事前に計算できるのであれば使用可能です。例えば、ヘッダーの高さが固定で、その下のアイテムの高さも固定といった場合です。
例えば、すべてのアイテムの高さが50
ピクセルである垂直方向のFlatList
の場合、getItemLayout
は以下のように実装できます。
import React from 'react';
import { FlatList, View, Text, StyleSheet } from 'react-native';
const DATA = Array.from({ length: 1000 }, (_, i) => ({ id: String(i), title: `Item ${i}` }));
const ITEM_HEIGHT = 50; // 各アイテムの固定の高さ
const MyFlatList = () => {
const renderItem = ({ item }) => (
<View style={styles.item}>
<Text style={styles.title}>{item.title}</Text>
</View>
);
return (
<FlatList
data={DATA}
renderItem={renderItem}
keyExtractor={(item) => item.id}
getItemLayout={(data, index) => (
{
length: ITEM_HEIGHT, // 各アイテムの高さ
offset: ITEM_HEIGHT * index, // 先頭からのオフセット
index,
}
)}
/>
);
};
const styles = StyleSheet.create({
item: {
height: ITEM_HEIGHT,
backgroundColor: '#f9c2ff',
padding: 20,
marginVertical: 8,
marginHorizontal: 16,
justifyContent: 'center',
alignItems: 'center',
},
title: {
fontSize: 16,
},
});
export default MyFlatList;
アイテムの高さ/幅が不正確な場合 (Offset/Length Calculation Mismatch)
これは最も一般的なエラーです。getItemLayout
で返されるlength
(高さまたは幅)やoffset
が、実際のアイテムのレンダリングサイズと一致しない場合に発生します。
症状:
- 一部のアイテムが表示されない(特にリストの下部)。
scrollToIndex
やscrollToEnd
などのスクロールメソッドが、意図した位置にスクロールしない。- リストをスクロールすると、アイテム間に不自然な空白ができる、またはアイテムが重なって表示される。
原因:
numColumns
を使用している場合、各行のオフセット計算が複雑になる。horizontal
プロパティがtrue
なのに、length
に高さを指定している(幅を指定すべき)。- アイテム間に
margin
、padding
、border
などのスペースがあるにもかかわらず、offset
の計算にそれらが含まれていない。 getItemLayout
で指定したlength
が、実際のrenderItem
で描画されるアイテムの高さ(または幅)と異なる。
トラブルシューティング:
numColumns
の考慮:numColumns
を使用する場合、offset
の計算がより複雑になります。各アイテムの幅を考慮し、行の高さと列のインデックスに基づいて計算する必要があります。一般的には、numColumns
を使用する場合はgetItemLayout
の恩恵を受けにくいか、より高度な計算が必要になります。可能であれば、numColumns
とgetItemLayout
の併用は避けるか、慎重に実装を検討してください。
horizontal
プロパティを確認する:horizontal={true}
の場合は、length
をアイテムの幅、offset
をwidth * index
で計算します。
- デバッグ用のボーダーを追加する:
renderItem
で描画される各アイテムに一時的に目立つボーダー(例:borderWidth: 1, borderColor: 'red'
)を設定し、実際にどのくらいのスペースを占めているかを目視で確認します。
- マージン・パディングを考慮する:
- アイテム間隔も
length
に含めるか、offset
計算に加えます。 - 例:
length: ITEM_HEIGHT + ITEM_MARGIN_VERTICAL
- 例:
offset: (ITEM_HEIGHT + ITEM_MARGIN_VERTICAL) * index
- アイテム間隔も
- 正確なアイテムサイズを把握する:
renderItem
で描画されるコンポーネントに固定の高さを与え、その値をgetItemLayout
のlength
に設定します。StyleSheet.create
で定義したスタイルから高さを取得するか、コンポーネントのプロパティとして渡すなどして、一貫した値を確保します。
アイテムのサイズが動的に変わる場合
getItemLayout
は、アイテムのサイズが事前にわかる場合に最適化効果を発揮します。アイテムの高さや幅がデータによって動的に変わる場合、getItemLayout
は適切ではありません。
症状:
- コンテンツが動的に変化したときに、リストのレイアウトが正しく更新されない。
- 上記の「高さ/幅の不正確」と同じような表示の乱れが発生する。
原因:
getItemLayout
で固定値を返しているが、実際のアイテムのコンテンツ(例: テキストの行数、画像のロードなど)によって高さが変わる。
トラブルシューティング:
FlashList
の検討:- Shopifyが提供する
FlashList
は、動的な高さのアイテムに対するパフォーマンスをより良く処理できるように設計されています。もしパフォーマンスが最優先で、動的な高さが避けられない場合は、FlashList
への移行を検討する価値があります。
- Shopifyが提供する
onLayout
で動的な高さを測定する:- どうしても
getItemLayout
を使いたいが、一部のアイテムの高さが動的な場合は、各アイテム内でonLayout
イベントを使用して実際の高さを測定し、それを親コンポーネントに伝える方法も考えられます。ただし、これは非常に複雑になり、パフォーマンスメリットを打ち消す可能性があります。通常は推奨されません。
- どうしても
getItemLayout
を使用しない:- 動的な高さのアイテムがある場合、
getItemLayout
の使用は避けるべきです。FlatList
は、アイテムがレンダリングされた後に自動的にそのサイズを測定します。パフォーマンスが問題になる場合は、他の最適化手法(windowSize
、initialNumToRender
、maxToRenderPerBatch
、removeClippedSubviews
など)を検討してください。
- 動的な高さのアイテムがある場合、
getItemLayoutが呼ばれない、または効果がないように見える場合
症状:
console.log
などでgetItemLayout
内の処理が呼ばれていないように見える。getItemLayout
を実装したのに、スクロールパフォーマンスに変化が見られない。
原因:
- 不適切な
keyExtractor
など、他のFlatList
のプロパティ設定が影響している可能性。 - デバッグモード(リモートデバッグ)を使用している場合、パフォーマンスが低下するため、
getItemLayout
の効果が分かりにくいことがある。 FlatList
がgetItemLayout
の情報を利用する前に、すでにすべてのアイテムがレンダリングされている(例: リストが非常に短い場合)。
トラブルシューティング:
- 他の最適化プロパティを確認する:
keyExtractor
が適切に設定されているか確認します。一意のキーが設定されていないと、FlatList
はアイテムの再レンダリングや追跡を正しく行えません。initialNumToRender
、windowSize
、maxToRenderPerBatch
などのプロパティも、getItemLayout
の効果を補完したり、時には妨げたりする可能性があります。これらの値を調整してみることも検討します。
- 本番ビルドで試す:
- デバッグモードではなく、リリースビルド(またはHermesエンジンを有効にする)でパフォーマンスを評価します。デバッグモードではJavaScriptスレッドのパフォーマンスが低下するため、最適化の効果が隠れてしまうことがあります。
- リストのアイテム数を増やす:
getItemLayout
の真価は、数百または数千のアイテムがあるような長いリストで発揮されます。短いリストでは、その効果は体感しにくいかもしれません。
ごく稀に、getItemLayout
関数にindex
が-1
として渡されるという報告があります。これは、FlatList
内部の特殊なケースやバグが原因である可能性があります。
症状:
getItemLayout
内でindex
が-1
になることで、配列の参照エラーなどが発生する。
トラブルシューティング:
index
が-1
の場合のガード処理を追加する:- 確実な解決策ではないかもしれませんが、一時的な回避策として、
index
が-1
の場合は適切なデフォルト値(例:length: 0, offset: 0, index
)を返すように処理を追加します。
- 確実な解決策ではないかもしれませんが、一時的な回避策として、
getItemLayout={(data, index) => {
if (index === -1) {
// 例外的なケースへの対応
return { length: 0, offset: 0, index };
}
return {
length: ITEM_HEIGHT,
offset: ITEM_HEIGHT * index,
index,
};
}}
getItemLayout
は、リストアイテムの高さ(または幅)が事前にわかっている場合に、FlatList
のスクロールパフォーマンスを大幅に向上させるために使用されます。
基本例:固定の高さのアイテム
最も一般的なケースで、すべてのリストアイテムが同じ固定の高さを持ちます。
import React from 'react';
import { FlatList, View, Text, StyleSheet } from 'react-native';
// サンプルデータ
const DATA = Array.from({ length: 1000 }, (_, i) => ({
id: String(i),
title: `アイテム ${i}`,
}));
// 各アイテムの固定の高さ
const ITEM_HEIGHT = 80;
const FixedHeightList = () => {
const renderItem = ({ item }) => (
<View style={styles.item}>
<Text style={styles.title}>{item.title}</Text>
</View>
);
return (
<FlatList
data={DATA}
renderItem={renderItem}
keyExtractor={(item) => item.id}
// ここが重要:getItemLayoutの実装
getItemLayout={(data, index) => ({
length: ITEM_HEIGHT, // アイテムの高さ
offset: ITEM_HEIGHT * index, // 先頭からのオフセット
index, // アイテムのインデックス
})}
initialNumToRender={10} // 初期表示数を設定(パフォーマンス最適化)
windowSize={21} // レンダリングされるビューポート外のアイテム数を設定
maxToRenderPerBatch={10} // 各バッチでレンダリングするアイテム数
/>
);
};
const styles = StyleSheet.create({
item: {
height: ITEM_HEIGHT, // getItemLayoutで指定した高さと合わせる
backgroundColor: '#f9c2ff',
padding: 20,
marginVertical: 8, // マージンも考慮する必要がある場合は、ITEM_HEIGHTに含める
marginHorizontal: 16,
justifyContent: 'center',
alignItems: 'flex-start',
borderRadius: 8,
},
title: {
fontSize: 18,
fontWeight: 'bold',
color: '#333',
},
});
export default FixedHeightList;
ポイント:
offset
はITEM_HEIGHT * index
で計算されます。これは、そのアイテムがリストの先頭からどれだけ離れているかを示します。ITEM_HEIGHT
という定数でアイテムの高さを定義し、それをFlatList
のスタイルとgetItemLayout
の両方で使用しています。これにより、値の一貫性が保たれます。
例2:固定の高さ + アイテム間のマージン/セパレータ
アイテム間にマージンやセパレータ(区切り線)がある場合、その分もoffset
の計算に含める必要があります。
import React from 'react';
import { FlatList, View, Text, StyleSheet, SafeAreaView } from 'react-native';
const DATA = Array.from({ length: 500 }, (_, i) => ({
id: String(i),
title: `アイテム ${i}`,
}));
const ITEM_BASE_HEIGHT = 60; // アイテム自体の高さ
const ITEM_MARGIN_VERTICAL = 10; // アイテムの上下マージン
const TOTAL_ITEM_HEIGHT = ITEM_BASE_HEIGHT + ITEM_MARGIN_VERTICAL; // マージンを含めた全体の高さ
const FixedHeightWithMarginList = () => {
const renderItem = ({ item }) => (
<View style={styles.item}>
<Text style={styles.title}>{item.title}</Text>
</View>
);
return (
<SafeAreaView style={{ flex: 1 }}>
<FlatList
data={DATA}
renderItem={renderItem}
keyExtractor={(item) => item.id}
getItemLayout={(data, index) => ({
length: TOTAL_ITEM_HEIGHT, // マージンを含めたアイテムの全長
offset: TOTAL_ITEM_HEIGHT * index, // その合計長を基準にオフセットを計算
index,
})}
/>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
item: {
height: ITEM_BASE_HEIGHT, // ここはアイテム自体の高さ
backgroundColor: '#add8e6',
padding: 15,
marginVertical: ITEM_MARGIN_VERTICAL / 2, // 上下のマージンを半分ずつ設定することで、アイテム間にTOTAL_ITEM_HEIGHTのマージンがあるように見せる
marginHorizontal: 16,
justifyContent: 'center',
borderRadius: 5,
},
title: {
fontSize: 16,
color: '#333',
},
});
export default FixedHeightWithMarginList;
ポイント:
marginVertical
は、TOTAL_ITEM_HEIGHT
がアイテム間のスペースを考慮するように、ITEM_MARGIN_VERTICAL / 2
として設定しています。これにより、各アイテムの上と下にITEM_MARGIN_VERTICAL / 2
のマージンがつき、結果的にアイテムとアイテムの間にはITEM_MARGIN_VERTICAL
のマージンが生まれます。getItemLayout
のlength
とoffset
には、このTOTAL_ITEM_HEIGHT
を使用します。ITEM_BASE_HEIGHT
とITEM_MARGIN_VERTICAL
を定義し、TOTAL_ITEM_HEIGHT
でアイテムの「占めるべき全体のスペース」を計算しています。
例3:ヘッダー/フッターがあり、アイテムの高さが固定の場合
リストに固定の高さのヘッダーやフッターがある場合も、getItemLayout
を適用できます。この場合、ヘッダー/フッターの高さもオフセット計算に含める必要があります。
import React from 'react';
import { FlatList, View, Text, StyleSheet, SafeAreaView } from 'react-native';
const DATA = Array.from({ length: 500 }, (_, i) => ({
id: String(i),
title: `アイテム ${i}`,
}));
const ITEM_HEIGHT = 70;
const HEADER_HEIGHT = 100;
const FOOTER_HEIGHT = 80;
const HeaderFooterList = () => {
const renderItem = ({ item }) => (
<View style={styles.item}>
<Text style={styles.title}>{item.title}</Text>
</View>
);
const renderHeader = () => (
<View style={styles.header}>
<Text style={styles.headerText}>これはヘッダーです</Text>
</View>
);
const renderFooter = () => (
<View style={styles.footer}>
<Text style={styles.footerText}>これはフッターです</Text>
</View>
);
return (
<SafeAreaView style={{ flex: 1 }}>
<FlatList
data={DATA}
renderItem={renderItem}
keyExtractor={(item) => item.id}
ListHeaderComponent={renderHeader}
ListFooterComponent={renderFooter}
getItemLayout={(data, index) => {
let offset = HEADER_HEIGHT; // ヘッダーの高さでオフセットを初期化
offset += index * ITEM_HEIGHT; // アイテム分のオフセットを追加
return {
length: ITEM_HEIGHT,
offset: offset,
index,
};
}}
/>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
header: {
height: HEADER_HEIGHT, // getItemLayoutで指定した高さと合わせる
backgroundColor: '#ffd700',
justifyContent: 'center',
alignItems: 'center',
padding: 20,
},
headerText: {
fontSize: 22,
fontWeight: 'bold',
},
item: {
height: ITEM_HEIGHT, // getItemLayoutで指定した高さと合わせる
backgroundColor: '#b0e0e6',
padding: 20,
marginVertical: 4,
marginHorizontal: 16,
justifyContent: 'center',
alignItems: 'flex-start',
borderRadius: 4,
},
title: {
fontSize: 16,
color: '#333',
},
footer: {
height: FOOTER_HEIGHT, // getItemLayoutではフッターの考慮は不要(リストの最後に自動的に配置されるため)
backgroundColor: '#98fb98',
justifyContent: 'center',
alignItems: 'center',
padding: 20,
},
footerText: {
fontSize: 18,
fontWeight: 'bold',
},
});
export default HeaderFooterList;
ポイント:
- フッターの高さは
getItemLayout
の計算には直接影響しません。FlatList
はフッターをリストの最後に自動的に配置するためです。 getItemLayout
のoffset
計算で、まずHEADER_HEIGHT
分を追加しています。これにより、すべてのアイテムがヘッダーの下から開始されるようにオフセットが調整されます。
horizontal
プロパティをtrue
に設定した場合、getItemLayout
のlength
はアイテムの「幅」を、offset
はリストの「左端」からの距離を示します。
import React from 'react';
import { FlatList, View, Text, StyleSheet, SafeAreaView, Dimensions } from 'react-native';
const DATA = Array.from({ length: 50 }, (_, i) => ({
id: String(i),
title: `横アイテム ${i}`,
}));
const ITEM_WIDTH = Dimensions.get('window').width * 0.7; // 画面幅の70%をアイテムの幅とする
const ITEM_MARGIN_HORIZONTAL = 10;
const TOTAL_ITEM_WIDTH = ITEM_WIDTH + ITEM_MARGIN_HORIZONTAL;
const HorizontalList = () => {
const renderItem = ({ item }) => (
<View style={styles.item}>
<Text style={styles.title}>{item.title}</Text>
</View>
);
return (
<SafeAreaView style={styles.container}>
<Text style={styles.listTitle}>水平スクロールリスト</Text>
<FlatList
data={DATA}
renderItem={renderItem}
keyExtractor={(item) => item.id}
horizontal // 水平方向スクロールを有効にする
showsHorizontalScrollIndicator={false} // スクロールインジケーターを非表示
getItemLayout={(data, index) => ({
length: TOTAL_ITEM_WIDTH, // アイテムの幅(マージン含む)
offset: TOTAL_ITEM_WIDTH * index, // 左端からのオフセット
index,
})}
/>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
paddingTop: 20,
},
listTitle: {
fontSize: 20,
fontWeight: 'bold',
marginBottom: 10,
marginLeft: 16,
},
item: {
width: ITEM_WIDTH, // getItemLayoutで指定した幅と合わせる
height: 150, // 高さ
backgroundColor: '#87ceeb',
padding: 15,
marginHorizontal: ITEM_MARGIN_HORIZONTAL / 2, // 左右のマージンを半分ずつ
justifyContent: 'center',
alignItems: 'center',
borderRadius: 10,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.2,
shadowRadius: 3,
elevation: 5,
},
title: {
fontSize: 18,
fontWeight: 'bold',
color: '#fff',
textAlign: 'center',
},
});
export default HorizontalList;
ポイント:
- スタイルシートの
item
でもwidth
プロパティを設定し、getItemLayout
と一致させる必要があります。 getItemLayout
のlength
にはアイテムの幅を、offset
にはwidth * index
を使用します。horizontal
プロパティをtrue
に設定しています。
これらの例は、getItemLayout
をさまざまなシナリオでどのように使用するかを示しています。最も重要なことは、getItemLayout
に渡すlength
とoffset
の値が、実際に画面にレンダリングされるアイテムのサイズと位置に正確に一致していることを確認することです。これにより、FlatList
はアイテムの測定処理をスキップし、スムーズで高性能なスクロールを実現できます。
React NativeのFlatList
におけるgetItemLayout
は、リストのアイテムが固定のサイズを持つ場合に最高のパフォーマンスを発揮する最適化手法です。しかし、すべてのリストでアイテムのサイズが固定とは限りません。アイテムの高さや幅が動的に変わる場合、getItemLayout
は使用できません(または、使用すると問題が発生します)。
ここでは、getItemLayout
が使えない、または適さない場合の代替手段と、FlatListの他の最適化手法について説明します。
getItemLayout以外のFlatListの最適化プロパティ
getItemLayout
を使用できない場合でも、FlatList
には他にもパフォーマンスを向上させるためのプロパティがいくつかあります。これらは、getItemLayout
と組み合わせて使うこともできます。
-
renderItem
の最適化:- 説明:
renderItem
関数内で直接アロー関数を使用するのを避け、コンポーネントの外で定義するか、useCallback
でメモ化することを推奨します。これにより、FlatList
が再レンダリングされるたびに関数が再作成されるのを防ぎます。 - アイテムコンポーネントの軽量化: 各リストアイテムのコンポーネントは、できるだけシンプルで、ネストが少なく、計算コストの低いものであるべきです。複雑なロジックや重い画像処理は、アイテムの詳細画面に移動させるなどして、リストアイテム自体の負担を減らします。
React.memo
(またはPureComponent
) の使用:renderItem
で描画されるアイテムコンポーネントが複雑で、propsが頻繁に変わらない場合は、React.memo
(関数コンポーネントの場合) やPureComponent
(クラスコンポーネントの場合) でメモ化することで、不要な再レンダリングを防ぐことができます。ただし、propsが複雑だったり、頻繁に変わる場合は、かえってパフォーマンスが低下する可能性があるので注意が必要です。
- 説明:
-
keyExtractor
:- 説明: 各リストアイテムの一意のキーを抽出するための関数です。これは
FlatList
だけでなく、Reactのリストレンダリング全般にわたって非常に重要です。 - 効果: Reactがアイテムの追加、削除、並べ替えを効率的に追跡し、不要な再レンダリングを避けることができます。キーが不適切だと、リストのパフォーマンスが大幅に低下したり、状態が正しく保持されなかったりする原因になります。
- 例:
keyExtractor={(item) => item.id}
- 説明: 各リストアイテムの一意のキーを抽出するための関数です。これは
-
removeClippedSubviews
:- 説明:
true
に設定すると、ビューポート外のビューをネイティブのビュー階層からデタッチします。これにより、メインスレッドでの描画処理の時間を削減し、フレーム落ちのリスクを減らすことができます。 - 注意点: 特定の状況下ではバグを引き起こす可能性があり、推奨されない場合があります。特に、複雑なネストされたビューや、動的な高さのアイテムがある場合には注意が必要です。React Nativeのドキュメントでは、このプロパティの使用は推奨されていません。
- 説明:
-
updateCellsBatchingPeriod
:- 説明:
maxToRenderPerBatch
によって新しいアイテムのレンダリングを開始するまでの遅延時間(ミリ秒)を指定します。デフォルトは50msです。 - 効果: この値を調整することで、スクロール中のUIの応答性とアイテムの表示速度のバランスを取ることができます。
- 説明:
-
maxToRenderPerBatch
:- 説明: スクロール中に、一度にレンダリングするアイテムの最大数を指定します。デフォルトは10です。
- 効果: この値を大きくすると、スクロール時のブランクエリアが減りますが、一度に多くのアイテムがレンダリングされるため、JavaScriptスレッドがブロックされ、反応性が低下する可能性があります。
- 例:
maxToRenderPerBatch={5}
-
windowSize
:- 説明: ビューポート(画面に表示されている領域)の上下に、どれだけのアイテムをレンダリングしておくかを制御します。デフォルト値は21(現在のビューポート+上下にそれぞれ10ビューポート分)です。
- 効果:
windowSize
を大きくすると、スクロール時にブランクエリアが見える可能性が減りますが、同時にメモリ使用量が増加し、レンダリング負荷も上がります。小さくするとメモリ使用量は減りますが、ブランクエリアが発生しやすくなります。 - 例:
windowSize={10}
(現在のビューポートの上下にそれぞれ4ビューポート分)
-
initialNumToRender
:- 説明: 最初にレンダリングするアイテムの数を指定します。画面に表示される最小限のアイテム数を設定することで、初回ロード時の時間を短縮できます。
- 注意点: 少なすぎると、初回表示時に画面が空白になる「ブランクエリア」が発生する可能性があります。多すぎると初回ロードが遅くなります。適切な値は、デバイスの画面サイズやアイテムの高さによって調整する必要があります。
- 例:
initialNumToRender={10}
getItemLayout
が使えない(アイテムの高さが動的)場合の主な代替手段は以下の通りです。
-
measureLayout
を使用して動的に高さを測定し、scrollToOffset
でスクロールする (複雑):- これは非常に高度で、通常は推奨されません。各アイテムがレンダリングされた後に
onLayout
コールバックでその高さを測定し、その情報を保持する、という複雑なロジックを実装する必要があります。 - その後、
scrollToIndex
の代わりにscrollToOffset
を使用して、計算されたオフセットに基づいてスクロールします。しかし、これもレイアウトの変更が発生するたびに再計算が必要となり、パフォーマンスの問題を根本的に解決するものではありません。
- これは非常に高度で、通常は推奨されません。各アイテムがレンダリングされた後に
-
ScrollView
を使用する (非推奨):- リストのアイテム数が非常に少なく(数十個程度)、すべてのアイテムが一度にレンダリングされてもパフォーマンスに問題がない場合にのみ検討できます。
ScrollView
はすべてのアイテムを一度にレンダリングするため、アイテム数が多い場合にメモリ使用量が増加し、パフォーマンスが著しく低下します。これは仮想化リスト(FlatList
やSectionList
など)の利点を完全に打ち消します。- 警告:
FlatList
やSectionList
のようなVirtualizedList
ベースのコンポーネントを、同じ方向のScrollView
の中にネストすることは、パフォーマンス上の問題や警告の原因となるため、避けるべきです。
-
FlashList
の検討 (Shopify製):FlashList
は、Shopifyが開発したFlatList
の代替となる高性能なリストコンポーネントです。動的な高さのアイテムに対するパフォーマンスを大幅に改善するように設計されています。FlatList
と非常に似たAPIを持っているため、既存のFlatList
を置き換えることが比較的容易です。FlashList
は、getItemLayout
のような事前のサイズ情報なしに、より効率的な仮想化とメモリ管理を実現します。特に、アイテムの高さが動的で、かつリストが非常に長い場合に非常に有効です。
-
FlatList
のデフォルトの動作に任せる:getItemLayout
を指定しない場合、FlatList
はアイテムがレンダリングされた後にそのサイズを測定します。これは、アイテムの高さが動的である場合の標準的なアプローチです。- 上記の「
FlatList
の最適化プロパティ」を最大限に活用し、初期表示数を調整したり、windowSize
を最適化したりすることで、ある程度のパフォーマンスは確保できます。
getItemLayout
が最適な選択肢であるのは、リストアイテムの高さが事前にわかっていて、固定である場合です。もしアイテムの高さが動的な場合は、以下の選択肢を検討してください。
FlatList
の他の最適化プロパティを最大限に活用する。- 高性能なリストが必要で、特にアイテムの高さが動的である場合は、
FlashList
への移行を強く検討する。 - リストが非常に短い場合に限り、
ScrollView
も選択肢になりえますが、ほとんどのケースではFlatList
が推奨されます。