通常React Nativeでリストを表示する場合、FlatListコンポーネントが使われるが、SectionListではリスト毎にタイトルを設定したり、個別の関数を適用したりすることができる。
以前、下記の記事で作ったニュースアプリに手を加える形で、SectionListの使い方を見ていこう。
RedditのスレをAPIで取得してリスト形式で表示するアプリだが、このリストを「人気」、「議論中」とそれぞれリストを分割して表示する。
それぞれのAPIに対応するURLを用意する
まずはAPIに対応するURLを配列形式で用意する。
01 02 03 04 05 06 07 08 09 10 | let list = [ { uri: "https://www.reddit.com/r/newsokur/hot.json" , title: "人気" }, { uri: "https://www.reddit.com/r/newsokur/controversial.json" , title: "議論中" } ]; |
Promise形式でHTTPリクエストを行う
複数のURL(スレッド)からデータを取得するので、Promise形式でHTTPリクエストを行うメソッドを用意する。
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 | _fetchThread(item) { return new Promise((resolve, reject) => { fetch(item.uri) .then((response) => response.json()) .then((responseJson) => { let threads = responseJson.data.children.slice(0, 5); threads = threads.map(i => { i.key = i.data.url; return i; }) return resolve({data: threads, title: item.title}); }) . catch ((error) => { return reject(error); }) }); } |
この_fetchThreadメソッドを、非同期に並列で実行するためにPromise.all()を使った「fetchThread」メソッドを用意する。
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 | fetchThread() { let list = [ { uri: "https://www.reddit.com/r/newsokur/hot.json" , title: "人気" }, { uri: "https://www.reddit.com/r/newsokur/controversial.json" , title: "議論中" } ]; Promise.all(list.map(i => this ._fetchThread(i))) .then(r => { this .setState({threads: r}); this .setState({isLoading: false }); }). catch (e => { console.warn(e); }) } |
リクエストが成功するとthenメソッドが呼び出され、state変数に記事データがセットされる。
コード全文
ここまでで用意したfetchThreadメソッドは、マウント時に実行されるようcompoentDidMount()内で実行されるようにする。
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 | import React, { Component } from 'react' ; import { StyleSheet, Text, View, SectionList, Image, Dimensions, ActivityIndicator } from 'react-native' ; export default class App extends Component { constructor() { super (); this .state = { isLoading: true , threads: [], } } componentDidMount() { this .fetchThread(); } _fetchThread(item) { return new Promise((resolve, reject) => { fetch(item.uri) .then((response) => response.json()) .then((responseJson) => { let threads = responseJson.data.children.slice(0, 5); threads = threads.map(i => { i.key = i.data.url; return i; }) return resolve({data: threads, title: item.title}); }) . catch ((error) => { return reject(error); }) }); } fetchThread() { let list = [ { uri: "https://www.reddit.com/r/newsokur/hot.json" , title: "人気" }, { uri: "https://www.reddit.com/r/newsokur/controversial.json" , title: "議論中" } ]; Promise.all(list.map(i => this ._fetchThread(i))) .then(r => { this .setState({threads: r}); this .setState({isLoading: false }); }). catch (e => { console.warn(e); }) } render() { const { threads, isLoading } = this .state; const { width } = Dimensions.get( 'window' ); return ( <View style={styles.container}> {isLoading ? <ActivityIndicator /> : <SectionList renderItem={thread => { return ( <View style={{ flex: 1, flexDirection: 'row' , width: '100%' }}> <Image style={{width: 50, height: 50}} source={{uri: thread.item.data.thumbnail}} /> <Text style={{width: width - 50}} key={thread.key}>{thread.item.data.title}</Text> </View> ); }} renderSectionHeader={({section}) => <Text>{section.title}</Text>} sections={threads} /> } </View> ) } } const styles = StyleSheet.create({ container: { flex: 1, justifyContent: 'center' , alignItems: 'center' , backgroundColor: '#F5FCFF' , paddingTop: 20, } }); |
expo startでアプリを実行すると、人気・議論中それぞれに分かれた記事リストが表示される。
