React Native FlatList renderItemで画像や複雑なレイアウトを表示する
renderItem の役割
FlatList
は、与えられたデータの配列に基づいてリストを表示しますが、それぞれのデータ要素をどのように画面に表示するかは renderItem
関数によって決定されます。この関数は、データ配列の各アイテムに対応する React コンポーネントまたは JSX 要素を返す必要があります。
renderItem
関数の構造
renderItem
関数は、通常、以下のような構造を持ちます。
renderItem={({ item, index, separators }) => {
// ここに各アイテムの描画ロジックを記述します
return (
// 表示したいコンポーネントまたはJSX
<View>
<Text>{item.name}</Text>
<Text>価格: {item.price}円</Text>
{/* その他の表示要素 */}
</View>
);
}}
引数について
renderItem
関数は、オブジェクトを引数として受け取ります。このオブジェクトには、以下のプロパティが含まれています。
-
separators
: アイテム間の区切り線を制御するためのいくつかの関数を含むオブジェクトです。主に、ハイライトや非ハイライトの表示に使用されます。separators.highlight()
: 区切り線をハイライト表示します。separators.unhighlight()
: 区切り線のハイライト表示を解除します。separators.updateProps('leading' | 'trailing', newProps)
: 先頭または末尾の区切り線のプロパティを更新します。
-
index
: 現在のアイテムの配列内でのインデックス(位置)です。0から始まる整数値です。 -
item
: 現在描画しようとしているデータ配列の要素そのものです。例えば、データがオブジェクトの配列であれば、このitem
はその個々のオブジェクトになります。
具体的な使用例
例えば、以下のようなデータ配列があるとします。
const products = [
{ id: '1', name: 'リンゴ', price: 100 },
{ id: '2', name: 'バナナ', price: 150 },
{ id: '3', name: 'オレンジ', price: 200 },
];
このデータを FlatList
で表示する場合、renderItem
は以下のようになります。
<FlatList
data={products}
renderItem={({ item }) => (
<View style={{ padding: 10, borderBottomWidth: 1, borderColor: '#ccc' }}>
<Text style={{ fontWeight: 'bold' }}>{item.name}</Text>
<Text>価格: {item.price}円</Text>
</View>
)}
keyExtractor={(item) => item.id}
/>
この例では、renderItem
は各商品オブジェクト (item
) から name
と price
を取り出し、Text
コンポーネントで表示しています。各アイテムは View
で囲まれ、下部に区切り線が表示されるようにスタイルが設定されています。
重要なポイント
renderItem
が返すコンポーネントは、スタイルやイベントハンドラなど、通常の React コンポーネントと同様に扱うことができます。separators
を使用することで、アイテム間の区切り線の表示を細かく制御できます。index
を利用して、偶数行と奇数行で異なるスタイルを適用するなど、条件に応じた描画が可能です。item
プロパティを通じて、そのアイテムのデータにアクセスできます。renderItem
は、リストの各アイテムに対して呼び出されます。
renderItem が React 要素を返さない
エラーの状況
renderItem
関数内で return
ステートメントを忘れたり、null
や undefined
など、React が描画できない値を返したりする場合に発生します。
エラーメッセージの例
特に明確なエラーメッセージが出ないこともありますが、リストが何も表示されない、または予期しない動作をすることがあります。
トラブルシューティング
- 条件分岐などで
null
やundefined
を返す可能性がある場合は、それらのケースで適切な代替コンポーネントを返すように修正してください。 return
する値が、<View>
、<Text>
などの有効な React コンポーネントまたは JSX 要素であることを確認してください。renderItem
関数内に必ずreturn
ステートメントがあるか確認してください。
例(誤り)
renderItem={({ item }) => {
// return を忘れている
<Text>{item.name}</Text>
}}
例(修正)
renderItem={({ item }) => {
return <Text>{item.name}</Text>;
}}
item プロパティの誤った使用
エラーの状況
renderItem
の引数である { item }
から、存在しないプロパティにアクセスしようとする場合に発生します。
エラーメッセージの例
「undefined is not an object (evaluating 'item.propertyName')」のようなエラーメッセージが表示されることがあります。
トラブルシューティング
- データ構造が複雑な場合や、API から取得したデータを使用している場合は、データの形式を
console.log
などで確認し、renderItem
で正しくアクセスできるように調整してください。 renderItem
内でitem
オブジェクトのプロパティにアクセスする際に、スペルミスがないか確認してください。FlatList
に渡しているdata
配列の要素構造(各オブジェクトのプロパティ名)を正確に把握してください。
例(誤り - 存在しないプロパティ 'title' にアクセス)
const data = [{ name: 'リンゴ' }, { name: 'バナナ' }];
// ...
renderItem={({ item }) => <Text>{item.title}</Text>}
例(修正)
const data = [{ name: 'リンゴ' }, { name: 'バナナ' }];
// ...
renderItem={({ item }) => <Text>{item.name}</Text>}
keyExtractor の未定義または不適切な定義
エラーの状況
FlatList
は、リストのアイテムを効率的に再描画するために、各アイテムに一意のキーが必要です。keyExtractor
プロパティが定義されていないか、一意でない値を返す関数が指定されている場合に、パフォーマンスの問題や予期しないレンダリングの挙動が発生することがあります。
エラーメッセージの例
特にエラーメッセージは表示されないことが多いですが、リストの更新が遅い、アイテムの順序が入れ替わるなどの問題が起こり得ます。
トラブルシューティング
- インデックスを
keyExtractor
に使用することは、アイテムの順序が変わる場合に問題を引き起こす可能性があるため、避けるべきです。 keyExtractor
には、各アイテムを一意に識別できる値を文字列として返す関数を指定してください。通常は、アイテムのid
プロパティなどを使用します。FlatList
には必ずkeyExtractor
プロパティを指定してください。
例(誤り - keyExtractor が未定義)
<FlatList
data={data}
renderItem={({ item }) => <Text>{item.name}</Text>}
/>
例(修正 - id プロパティを一意のキーとして使用)
const data = [{ id: '1', name: 'リンゴ' }, { id: '2', name: 'バナナ' }];
// ...
<FlatList
data={data}
renderItem={({ item }) => <Text>{item.name}</Text>}
keyExtractor={(item) => item.id}
/>
renderItem 内での複雑すぎるロジックやパフォーマンスの問題
エラーの状況
renderItem
内で非常に複雑な計算や処理を行うと、リストのスクロールが遅くなったり、アプリケーションの応答性が悪化したりする可能性があります。
トラブルシューティング
- 必要に応じて、
shouldComponentUpdate
(クラスコンポーネントの場合) やReact.memo
(関数コンポーネントの場合) を使用して、不必要な再レンダリングを防ぐことを検討してください。 - 複雑なロジックは、
renderItem
の外で事前に計算したり、別のヘルパー関数に分離したりすることを検討してください。 renderItem
内では、主に描画に必要な処理のみを行うように心がけてください。
スタイルの適用に関する問題
エラーの状況
renderItem
内で定義したスタイルが意図通りに適用されない場合があります。
トラブルシューティング
StyleSheet.create
を使用してスタイルを定義し、再利用することを推奨します。- 親コンポーネントや他のスタイルの影響を受けていないか確認してください。スタイルの優先順位や継承関係を考慮する必要があります。
- スタイルオブジェクトのプロパティ名が正しいか、値が適切かを確認してください。
separators の誤った使用
エラーの状況
renderItem
の引数に含まれる separators
オブジェクトの関数 (highlight
, unhighlight
, updateProps
) を誤って使用すると、区切り線の表示が意図しないものになることがあります。
トラブルシューティング
separators.updateProps()
を使用する場合は、更新したい区切り線の種類 ('leading'
または'trailing'
) と、適用したい新しいプロパティを正しく指定してください。separators.highlight()
およびseparators.unhighlight()
は、区切り線の表示状態を一時的に変更するための関数です。適切なタイミングで使用してください(例えば、アイテムが押されたとき)。
条件付きレンダリングのミス
エラーの状況
renderItem
内で条件に基づいて異なるコンポーネントをレンダリングする場合、条件式の記述ミスや、すべての条件に対応するレンダリング処理が記述されていない場合に、予期しない表示になることがあります。
if-else
文や三項演算子 (? :
) を使用して、すべての可能性のある条件に対応したレンダリング処理を記述してください。- 条件式が意図した通りに評価されるか、
console.log
などで確認してください。
例1: 基本的なテキスト表示
最も基本的な例として、シンプルなテキストのリストを表示するケースです。
import React from 'react';
import { FlatList, StyleSheet, Text, View } from 'react-native';
const data = [
{ id: '1', title: '最初のアイテム' },
{ id: '2', title: '2番目のアイテム' },
{ id: '3', title: '3つ目のアイテム' },
];
const Item = ({ title }) => (
<View style={styles.item}>
<Text style={styles.title}>{title}</Text>
</View>
);
const App = () => {
const renderItem = ({ item }) => (
<Item title={item.title} />
);
return (
<View style={styles.container}>
<FlatList
data={data}
renderItem={renderItem}
keyExtractor={item => item.id}
/>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
paddingTop: 22,
},
item: {
backgroundColor: '#f9c2ff',
padding: 20,
marginVertical: 8,
marginHorizontal: 16,
},
title: {
fontSize: 24,
},
});
export default App;
解説
keyExtractor
: 各アイテムに一意のキーを提供するための関数です。ここでは、各アイテムのid
をキーとして使用しています。renderItem
:FlatList
のrenderItem
プロパティに渡される関数です。- 引数として
{ item }
を受け取ります。これは、data
配列の現在の要素です。 <Item title={item.title} />
を返すことで、各データ要素に対応したItem
コンポーネントをレンダリングしています。
- 引数として
Item
コンポーネント: 個々のアイテムを描画するシンプルなコンポーネントです。title
プロパティを受け取り、Text
で表示します。data
: 表示するデータの配列です。各オブジェクトはid
とtitle
プロパティを持っています。
例2: 画像とテキストの表示
より複雑な例として、データに画像情報も含まれている場合に、画像とテキストを組み合わせて表示するケースです。
import React from 'react';
import { FlatList, StyleSheet, Text, View, Image } from 'react-native';
const data = [
{ id: '1', name: '猫', imageUrl: 'https://placekitten.com/50/50' },
{ id: '2', name: '犬', imageUrl: 'https://placedog.com/50/50' },
{ id: '3', name: '鳥', imageUrl: 'https://via.placeholder.com/50' },
];
const Item = ({ name, imageUrl }) => (
<View style={styles.item}>
<Image source={{ uri: imageUrl }} style={styles.image} />
<Text style={styles.name}>{name}</Text>
</View>
);
const App = () => {
const renderItem = ({ item }) => (
<Item name={item.name} imageUrl={item.imageUrl} />
);
return (
<View style={styles.container}>
<FlatList
data={data}
renderItem={renderItem}
keyExtractor={item => item.id}
/>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
paddingTop: 22,
},
item: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: '#e0f7fa',
padding: 10,
marginVertical: 8,
marginHorizontal: 16,
},
image: {
width: 50,
height: 50,
marginRight: 10,
},
name: {
fontSize: 18,
},
});
export default App;
解説
renderItem
:item
オブジェクトからname
とimageUrl
を取り出し、Item
コンポーネントに props として渡しています。Item
コンポーネント:Image
コンポーネントを使用して、imageUrl
から画像を読み込んで表示しています。flexDirection: 'row'
とalignItems: 'center'
を使用して、画像とテキストを横並びに配置しています。data
: 各オブジェクトにname
とimageUrl
プロパティが追加されました。
例3: インデックスを利用した条件付きレンダリング
renderItem
の引数には index
も含まれています。これを利用して、リストの偶数行と奇数行で異なるスタイルを適用する例です。
import React from 'react';
import { FlatList, StyleSheet, Text, View } from 'react-native';
const data = Array.from({ length: 10 }, (_, i) => ({ id: String(i), value: `アイテム ${i + 1}` }));
const Item = ({ value, index }) => (
<View style={[styles.item, index % 2 === 0 ? styles.evenItem : styles.oddItem]}>
<Text style={styles.value}>{value}</Text>
</View>
);
const App = () => {
const renderItem = ({ item, index }) => (
<Item value={item.value} index={index} />
);
return (
<View style={styles.container}>
<FlatList
data={data}
renderItem={renderItem}
keyExtractor={item => item.id}
/>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
paddingTop: 22,
},
item: {
padding: 20,
marginVertical: 8,
marginHorizontal: 16,
},
evenItem: {
backgroundColor: '#bbdefb', // 偶数行の色
},
oddItem: {
backgroundColor: '#e1f5fe', // 奇数行の色
},
value: {
fontSize: 16,
},
});
export default App;
解説
Item
コンポーネント:index
プロパティを受け取り、index % 2 === 0
で偶数行かどうかを判定し、条件に応じて異なるスタイル (styles.evenItem
またはstyles.oddItem
) を適用しています。renderItem
: 引数として{ item, index }
を受け取ります。
例4: separators
を使用した区切り線のカスタマイズ
renderItem
の引数には separators
オブジェクトも含まれています。これを利用して、アイテムがハイライトされたときに区切り線のスタイルを変更する例です。
import React from 'react';
import { FlatList, StyleSheet, Text, View, TouchableHighlight } from 'react-native';
const data = Array.from({ length: 5 }, (_, i) => ({ id: String(i), text: `アイテム ${i + 1}` }));
const Item = ({ item, index, separators }) => (
<TouchableHighlight
onPress={() => {
separators.highlight();
setTimeout(() => separators.unhighlight(), 1000); // 1秒後にハイライトを解除
}}
underlayColor="lightgray"
>
<View style={styles.item}>
<Text style={styles.text}>{item.text}</Text>
</View>
</TouchableHighlight>
);
const App = () => {
const renderItem = ({ item, index, separators }) => (
<Item item={item} index={index} separators={separators} />
);
const ItemSeparator = () => (
<View style={styles.separator} />
);
return (
<View style={styles.container}>
<FlatList
data={data}
renderItem={renderItem}
keyExtractor={item => item.id}
ItemSeparatorComponent={ItemSeparator}
/>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
paddingTop: 22,
},
item: {
backgroundColor: '#fff',
padding: 20,
},
text: {
fontSize: 16,
},
separator: {
height: 1,
backgroundColor: '#ccc',
},
});
export default App;
ItemSeparatorComponent
:FlatList
のプロパティで、アイテム間の区切り線を描画するコンポーネントを指定します。ここではシンプルな横線を表示するItemSeparator
コンポーネントを使用しています。Item
コンポーネント:TouchableHighlight
でラップし、onPress
イベントでseparators.highlight()
を呼び出して区切り線をハイライト表示し、setTimeout
後にseparators.unhighlight()
で解除しています。renderItem
: 引数として{ item, index, separators }
を受け取ります。
関数コンポーネントを直接 renderItem に記述する (インラインレンダリング)
renderItem
の内部で、個別のコンポーネントを定義せずに直接 JSX を記述する方法です。シンプルな表示の場合に便利ですが、ロジックが複雑になると可読性が低下する可能性があります。
import React from 'react';
import { FlatList, StyleSheet, Text, View } from 'react-native';
const data = [
{ id: '1', name: 'リンゴ' },
{ id: '2', name: 'バナナ' },
];
const App = () => {
return (
<View style={styles.container}>
<FlatList
data={data}
renderItem={({ item }) => (
<View style={styles.item}>
<Text style={styles.name}>{item.name}</Text>
</View>
)}
keyExtractor={item => item.id}
/>
</View>
);
};
const styles = StyleSheet.create({
container: { flex: 1, paddingTop: 20 },
item: { padding: 10, borderBottomWidth: 1, borderColor: '#ccc' },
name: { fontSize: 16 },
});
export default App;
解説
- シンプルな表示であれば可読性も保たれますが、複雑なロジックやスタイルが絡む場合は、可読性を損なう可能性があります。
Item
コンポーネントを別途定義する必要がないため、コードが短くなります。renderItem
プロパティに、アロー関数として直接 JSX を記述しています。
高階関数を利用して renderItem を生成する
アイテムの種類や状態に応じて異なるレンダリング処理を行いたい場合に、高階関数を使って renderItem
を動的に生成する方法です。
import React from 'react';
import { FlatList, StyleSheet, Text, View } from 'react-native';
const data = [
{ id: '1', type: 'text', value: 'テキストアイテム' },
{ id: '2', type: 'number', value: 123 },
];
const renderItemByType = () => ({ item }) => {
switch (item.type) {
case 'text':
return (
<View style={styles.item}>
<Text>{item.value}</Text>
</View>
);
case 'number':
return (
<View style={styles.item}>
<Text style={styles.number}>{item.value}</Text>
</View>
);
default:
return null;
}
};
const App = () => {
return (
<View style={styles.container}>
<FlatList
data={data}
renderItem={renderItemByType()}
keyExtractor={item => item.id}
/>
</View>
);
};
const styles = StyleSheet.create({
container: { flex: 1, paddingTop: 20 },
item: { padding: 10, borderBottomWidth: 1, borderColor: '#ccc' },
number: { fontWeight: 'bold', color: 'blue' },
});
export default App;
解説
- これにより、アイテムの種類に応じたレンダリングロジックを
renderItem
の外で管理できます。 - 内部の関数は、
item
のtype
プロパティに基づいて異なる JSX を返します。 renderItemByType
という関数は、別の関数(renderItem
としてFlatList
に渡される関数)を返します。
React.memo を使用したコンポーネントの最適化
renderItem
でレンダリングされるコンポーネントが、props の変更がない限り再レンダリングされるのを防ぐために React.memo
を使用する方法です。リストのパフォーマンス向上に役立ちます。
import React from 'react';
import { FlatList, StyleSheet, Text, View } from 'react-native';
const data = [
{ id: '1', name: 'リンゴ' },
{ id: '2', name: 'バナナ' },
];
const Item = React.memo(({ name }) => (
<View style={styles.item}>
<Text style={styles.name}>{name}</Text>
</View>
));
const App = () => {
const renderItem = ({ item }) => (
<Item name={item.name} />
);
return (
<View style={styles.container}>
<FlatList
data={data}
renderItem={renderItem}
keyExtractor={item => item.id}
/>
</View>
);
};
const styles = StyleSheet.create({
container: { flex: 1, paddingTop: 20 },
item: { padding: 10, borderBottomWidth: 1, borderColor: '#ccc' },
name: { fontSize: 16 },
});
export default App;
解説
- リストのアイテム数が多く、各アイテムのレンダリングコストが高い場合に、パフォーマンスの改善が期待できます。
- これにより、
Item
コンポーネントは、受け取るname
プロパティが前回のレンダリング時と異なる場合にのみ再レンダリングされます。 Item
コンポーネントをReact.memo()
でラップしています。
Render Props パターン
Render Props パターンを利用して、アイテムのデータを受け取り、どのようにレンダリングするかを親コンポーネントから提供する方法です。より柔軟なコンポーネント設計が可能になります。
import React from 'react';
import { FlatList, StyleSheet, View } from 'react-native';
const data = [
{ id: '1', value: 'テキストA' },
{ id: '2', value: 'テキストB' },
];
const MyListItemRenderer = ({ item }) => (
<View style={styles.item}>
<Text style={styles.text}>{item.value}</Text>
</View>
);
const App = ({ listItemRenderer }) => {
const renderItem = ({ item }) => listItemRenderer({ item });
return (
<View style={styles.container}>
<FlatList
data={data}
renderItem={renderItem}
keyExtractor={item => item.id}
/>
</View>
);
};
const styles = StyleSheet.create({
container: { flex: 1, paddingTop: 20 },
item: { padding: 10, borderBottomWidth: 1, borderColor: '#ccc' },
text: { fontSize: 16 },
});
const UsageExample = () => (
<App listItemRenderer={MyListItemRenderer} />
);
export default UsageExample;
- これにより、リストのアイテムのレンダリング方法を、
FlatList
を使用する親コンポーネント側で定義できます。 UsageExample
コンポーネントでは、実際にMyListItemRenderer
コンポーネントをlistItemRenderer
prop としてApp
に渡しています。renderItem
関数内で、このlistItemRenderer
を呼び出し、現在のitem
を引数として渡します。App
コンポーネントは、listItemRenderer
という関数 prop を受け取ります。