map()で展開することもできるが…
メモが保存されているデータとして、下のような形式のデータがあったとします。
const memoData = [
{
id: 1,
title: "メモのタイトル",
memo: "メモの内容"
},
{
id: 2,
title: "買い物メモ",
memo: "卵を1パック買う"
},
{
id: 3,
title: "持って行くもの",
memo: "筆記用具、手帳"
}
];
よく見かける、オブジェクト型のデータが配列になっている構造ですね。
このようなデータを展開して表示させたい場合、JavaScriptやReactの扱いに慣れているほどmap()メソッドを使えば良いと考えてしまいがちかと思います。
もちろん、React Nativeでもmap()メソッドを使ってデータを展開して表示させることは可能です。
下は、map()メソッドを使ってメモのデータを展開→表示させた例です。
import React from 'react';
import { View, StyleSheet, Text, SafeAreaView } from 'react-native';
const memoData = [
{
id: 1,
title: "メモのタイトル",
memo: "メモの内容"
},
{
id: 2,
title: "買い物メモ",
memo: "卵を1パック買う"
},
{
id: 3,
title: "持って行くもの",
memo: "筆記用具、手帳"
}
];
function Memos() {
const memo = memoData.map( (item) => {
return (
<View style={styles.memoContainer}>
<Text style={styles.memoTitle}>{item.title}</Text>
<Text style={styles.memoText}>{item.memo}</Text>
</View>
)
});
return (
<>
{memo}
</>
);
}
function App() {
return (
<SafeAreaView style={styles.container}>
<Memos/>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
margin: "5%",
justifyContent: "center",
},
memoContainer: {
marginTop: 32,
paddingBottom: 8,
borderBottomWidth: 1
},
memoTitle: {
fontSize: 24,
fontWeight: "bold"
},
memoText: {
fontSize: 16,
marginTop: 8
}
});
export default App;
表示結果
ちゃんとデータが展開されて表示されていますね。
ちなみにですが、map()メソッドの使い方よくわからないという方は下の記事で解説しているので、よかったら参考にしてください。
【JavaScript】オブジェクトの配列データをリストに展開する方法
表示結果に問題がないことからもわかるように、データがごく少量の場合は、map()メソッドを使うことに特に問題はありません。
しかし、データの量がもっと増えて、大量のデータを展開しなければならない場合は話が変わってきます。
データの量が多い場合は、map()メソッドではなく、React NativeのコアコンポーネントであるFlatListコンポーネントを使った方が良いです。
それはなぜなのか?
次の章では、map()を使ったデータの展開と、FlatListコンポーネントを使ったデータの展開の違いを確認していきましょう!
POINT!
- ・データの展開・表示は、Reactと同じようにmap()メソッドを使ってもできる!
- ・データの量が少ない場合は、map()メソッドを使った展開でも問題ないが…!
- ・データの量が多い場合は、FlatListコンポーネントを使った方が良い!(理由は次章)
map()とFlatListの違い
map()メソッドを使ってもデータを展開して表示することができるのに、FlatListをコンポーネントを使った方が良い(ことが多い)のはなぜでしょうか?
理由はいくつかあるのですが、最も重要なポイントはレンダリングの違いによるパフォーマンスの差であると言えます。
map()メソッドを用いたデータの展開では、全てのアイテムが一度に展開・レンダリングされてしまいます。
すべてのアイテムが一度にレンダリングされる=スマホの画面内に収まらない(表示する必要のない)画面外のデータまで一気にレンダリングされるということになるため、アプリの動作が重くなる原因となり、ユーザー体験が悪くなるリスクが高まります。
一方、FlatListを使ったデータの展開では、スマホの画面内に収まる分だけ(表示する必要があるアイテムだけ)をレンダリングする仕組みになっているので、大量のデータを効率的に扱うことができます。
Webページで言うところの、img要素のloading=”lazy”が自動的に適用されるようなものだと考えると、分かりやすいかもしれません。
もちろん、map()メソッドとFlatListの違いはパフォーマンスだけではありません。
FlatListコンポーネントには様々なprops(プロパティ)が用意されており、例えばリストの区切り線(ディバイダー)を指定するのに便利なItemSeparatorComponentといったプロパティを利用することができます。
つまり、よほどの理由がない限り、データを展開して表示させる必要がある場合は様々なメリットを享受できるFlatListを使っておいた方が間違いないと言えるでしょう。
ということで、FlatListの使い方を次の章で確認していきましょう!
POINT!
- ・map()を使った展開は、一度に全てのデータをレンダリングしてしまう!
- ・FlatListを使った展開は、必要なデータのみレンダリングしてくれる!
- ・React Nativeでデータを展開する必要がある場合、基本的にFlatListを採用しよう!
FlatListでデータを展開する
FlatListコンポーネントを使ってデータを展開→表示させる手順を確認していきましょう。
まず、どのデータを展開するかを明確に指定するために、FlatListコンポーネントのdata propに、元データを指定する必要があります(必須)。
今回のサンプルで言うと、オブジェクトが配列になっている memoData ですね。
また、展開したデータをどのようにレンダリングするかを指定するrenderItem propの指定も必須です。
逆に言えば、指定が必須なのはこの2つのpropsだけなので、これだけでとりあえずデータを展開して表示させることは可能です。
ただし、通常はデータを一意のkey(id)で管理し、データのソートや追加・削除がしやすいようにしておきたい場合が多いので、keyExtractor propも合わせて指定しておくと良いでしょう。
下はFlatListでデータを展開した例です。
import React from 'react';
import { View, StyleSheet, Text, SafeAreaView, FlatList } from 'react-native';
const memoData = [
{
id: 1,
title: "メモのタイトル",
memo: "メモの内容"
},
{
id: 2,
title: "買い物メモ",
memo: "卵を1パック買う"
},
{
id: 3,
title: "持って行くもの",
memo: "筆記用具、手帳"
}
];
function App() {
return (
<SafeAreaView style={styles.container}>
<FlatList
//メモデータをdata propsに指定
data={memoData}
//一意となるkeyをidで管理
keyExtractor={item => item.id}
//レンダリングされるViewを定義
renderItem={({item}) => {
return (
<View style={styles.memoContainer}>
<Text style={styles.memoTitle}>{item.title}</Text>
<Text style={styles.memoText}>{item.memo}</Text>
</View>
);
}}
/>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
margin: "5%",
justifyContent: "center",
},
memoContainer: {
marginTop: 32,
paddingBottom: 8,
borderBottomWidth: 1
},
memoTitle: {
fontSize: 24,
fontWeight: "bold"
},
memoText: {
fontSize: 16,
marginTop: 8
}
});
export default App;
表示結果
表示結果はmap()を使って展開したものと全く同じになっていますが、先述のようにレンダリングのされ方が根本的に異なるので、メモのデータが増えてもパフォーマンスが落ちにくくなっています。
また、FlatListで使用可能なpropであるItemSeparatorComponentを利用することで、リストとリストの間のセパレーター(ディバイダー)を設定するのも楽になります。
下は、リスト間の調整をItemSeparatorComponentで行った例です。
import React from 'react';
import { View, StyleSheet, Text, SafeAreaView, FlatList } from 'react-native';
const memoData = [
{
id: 1,
title: "メモのタイトル",
memo: "メモの内容"
},
{
id: 2,
title: "買い物メモ",
memo: "卵を1パック買う"
},
{
id: 3,
title: "持って行くもの",
memo: "筆記用具、手帳"
}
];
function Divider() {
return (
<View style={{borderBottomWidth: 2, height: 15}}/>
);
}
function App() {
return (
<SafeAreaView style={styles.container}>
<FlatList
//メモデータをdata propsに指定
data={memoData}
//一意となるkeyをidで管理
keyExtractor={item => item.id}
//セパレーターを設定
ItemSeparatorComponent={Divider}
//レンダリングされるViewを定義
renderItem={({item}) => {
return (
<View style={styles.memoContainer}>
<Text style={styles.memoTitle}>{item.title}</Text>
<Text style={styles.memoText}>{item.memo}</Text>
</View>
);
}}
/>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
margin: "5%",
justifyContent: "center",
},
memoContainer: {
marginTop: 16,
},
memoTitle: {
fontSize: 24,
fontWeight: "bold"
},
memoText: {
fontSize: 16,
marginTop: 8
}
});
export default App;
表示結果
コンポーネントを区切り線として利用できるので、とても便利ですね!
また、表示結果からもわかるように、ItemSeparatorComponentを使ってセパレーターを設定しておくと、各アイテムの最初(一番上)と最後(一番下)にはセパレーター用のコンポーネントがレンダリングされません。
普通、『アイテム間の区切り線は欲しいけれど、アイテムの一番上と一番下に区切り線は必要ない』というケースが多いので、この仕様はとても便利で画期的です。
React NativeではCSSのように柔軟なセレクタ指定ができない(:notや:first-childなどを使えない)ので、StyleSheet APIで区切り線を指定すると、このように最初(最後)のアイテムだけ区切り線をなくすのが難しくなります。
なので、できればリスト間の区切り線はItemSeparatorComponentを使って調整しておいた方が良いかなと思います。
なお、FlatListのrenderItemは、下のようにコンポーネントを分けて記述しても全く問題ありません。
というより、普通はこちらの方がスッキリしてコードが見やすくまとまるのでおすすめです。
import React from 'react';
import { View, StyleSheet, Text, SafeAreaView, FlatList } from 'react-native';
const memoData = [
{
id: 1,
title: "メモのタイトル",
memo: "メモの内容"
},
{
id: 2,
title: "買い物メモ",
memo: "卵を1パック買う"
},
{
id: 3,
title: "持って行くもの",
memo: "筆記用具、手帳"
}
];
function renderItem({item}) {
return (
<View style={styles.memoContainer}>
<Text style={styles.memoTitle}>{item.title}</Text>
<Text style={styles.memoText}>{item.memo}</Text>
</View>
);
}
function Divider() {
return (
<View style={{borderBottomWidth: 2, height: 15}}/>
);
}
function App() {
return (
<SafeAreaView style={styles.container}>
<FlatList
//メモデータをdata propsに指定
data={memoData}
//一意となるkeyをidで管理
keyExtractor={item => item.id}
//セパレーターを設定
ItemSeparatorComponent={Divider}
//レンダリングされるViewを定義
renderItem={renderItem}
/>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
margin: "5%",
justifyContent: "center",
},
memoContainer: {
marginTop: 16,
},
memoTitle: {
fontSize: 24,
fontWeight: "bold"
},
memoText: {
fontSize: 16,
marginTop: 8
}
});
export default App;
表示結果は先に紹介したものと全く同じなので省略します。
今回は、予め用意されたデータを展開して表示するまでに留めましたが、FlatListのrenderItemにデータを削除できるボタンを追加したり、useStateで状態管理を管理できるようにしたりすれば、より実際のアプリに近づけていくことができるので、余力がある方はぜひ挑戦してみてください!
POINT!
- ・data propに渡すデータを指定しよう(必須)!
- ・renderItem propに展開したデータをレンダリングするためのアイテム(コンポーネント)を指定しよう!(必須)
- ・ItemSeparatorComponentで区切り線としてレンダリングするコンポーネントを指定することも可能!(オプション)