こんにちは、個別指導塾コミット塾長、AWESOME開発担当の船津です。
引き続きLearning React Nativeを使って勉強していきます。
今回は前回の内容を踏まえつつ、ListViewを使ったアプリを作っていきます。
NewYork TimesのAPIを利用して、ジャンルごとに本の一覧を取得するアプリになります。
APIkeyはhttps://developer.nytimes.com/から取得して下さい メールアドレスだけで取得出来ます。
やりたいことは…
- 初期状態でも何らかの本の一覧が表示されている(今回はe-book-fictionの一覧)
- 入力欄に入力するとほんの一覧が更新される
- 存在しないカテゴリを選択すると、エラーページが表示される
用意するのは以下のファイルです。
index.ios.js
アプリの登録用
BookList.js
アプリの本体、ListViewを書きます
BookItem.js
ListViewの中身を書きます
まずはreact-native init BookList
をして新しいアプリの雛形を作ります。
早速 index.ios.js
を見てみましょう
import React, { Component } from 'react'; import { AppRegistry } from 'react-native'; var BookList = require('./BookList') AppRegistry.registerComponent('BookList', () => BookList);
次にBookList.js
'use strict' import React, { Component } from 'react'; import { StyleSheet, Text, View, Image, ListView, TextInput, } from 'react-native'; var BookItem = require('./BookItem'); var API_KEY = 'hogehogehoge'; var API_STEM = 'https://api.nytimes.com/svc/books/v3/lists' class BookList extends Component { constructor() { super(); // データソースを定義し、初期値を与えます var ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2 }); this.state = { query: 'e-book-fiction', dataSource: ds.cloneWithRows([]), error: false }; // onChangeアクションに対してthisをbindします。 this.onChange = this.onChange.bind(this); } onChange(event){ var query = event.nativeEvent.text; this.setState({ query: query }); this._refreshData(this.state.query); } //デバイスの立ち上がり時にのみ実行される関数です。初期クエリの'e-book-fiction'でデータを取得します。 componentDidMount() { this._refreshData(this.state.query); } // APIでデータを取得する cloneWithRowsで配列を読み込ませます。 _refreshData(query) { var endpoint = <code>MARKDOWN_HASH84f1883cbf8d62980fdf7a2691c1b5cbMARKDOWN_HASH</code> fetch(endpoint) .then((response) => response.json()) .then((rjson) => { console.log(rjson); this.setState({ dataSource: this.state.dataSource.cloneWithRows(rjson.results.books), error: false }); }) .catch((error) => { console.warn(error); this.setState({ error: true }); }); } //エラー時に表示するページ _renderError() { return ( <View style={styles.container}> <View style={styles.queryContainer}> <Text> No result for {this.state.query} Please input again </Text> <TextInput placeholder="Please input query" style={[styles.query, styles.mainText]} returnKeyType="go" // 入力が完了して、「go」ボタンを押した際に呼び出される関数を定義します。 onSubmitEditing={this.onChange}/> </View> </View> ); } //BookItem.jsでモジュール化したListViewを読み込みます _renderRow(rowData) { return <BookItem coverURL={rowData.book_image} title={rowData.title} author={rowData.author}/>; } // ヘッダー _renderHeader() { return ( <View style={styles.sectionDivider}> <Text style={styles.headingText}> Best Seller List </Text> </View> ); } // フッター _renderFooter() { return ( <View style={styles.sectionDivider}> <Text> Data from New York Times Best sellers list. </Text> </View> ); } // Viewを描画します render() { if(this.state.error){ return this._renderError(); } else { return ( <View style={styles.container}> <View style={styles.queryContainer}> <TextInput placeholder="Please input query" style={[styles.query, styles.mainText]} returnKeyType="go" onSubmitEditing={this.onChange}/> <Text style={styles.headingText}> Search results for {this.state.query} </Text> </View> // リストビューのパラメータを設定します。enableEmptySectionsはそのうち指定しなくてもよくなるかも <ListView enableEmptySections={true} dataSource={this.state.dataSource} renderRow={this._renderRow} renderHeader={this._renderHeader} renderFooter={this._renderFooter} /> </View> ); } } } const styles = StyleSheet.create({ container: { flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: '#F5FCFF', paddingTop: 24, }, list: { flex: 1, flexDirection: 'row', }, listContent: { flex: 1 , flexDirection: 'column', }, row: { flex: 1, fontSize: 24, padding: 42 , borderWidth: 1, borderColor: '#dddddd', }, sectionDivider: { padding: 8, backgroundColor: '#eeeeee', alignItems: 'center' }, headingText: { flex: 1, fontSize: 24, alignSelf: 'center', }, button: { flex: 1, padding: 10, borderColor: '#000', borderRadius: 4, borderWidth: 2, alignSelf: 'flex-end', textAlign: 'center', }, mainText: { fontSize: 16, color: '#000', alignItems: 'center', }, query: { alignSelf: 'center', marginTop: 30, width: 300 , height: 30, borderWidth: 1, borderColor: '#000', borderRadius: 4, marginBottom: 20, padding:5, }, }); module.exports = BookList;
続いてBookItem.js
です
'use strict' import React, { Component } from 'react'; import { StyleSheet, Text, View, Image, ListView, } from 'react-native'; class BookItem extends Component { //入力値のvalidationを行います。ここではstringでないと入力出来ないようになっています。 propTypes: { coverURL: React.propTypes.string.isRequired, author: React.propTypes.string.isRequired, title: React.propTypes.string.isRequired, } render() { return ( <View style={styles.bookItem}> <Image style={styles.cover} source={{uri: this.props.coverURL}}/> <View style={styles.info}> <Text style={styles.author}>{this.props.author}</Text> <Text style={styles.title}>{this.props.title}</Text> </View> </View> ) } } var styles = StyleSheet.create({ bookItem: { flex: 1, flexDirection: 'row', backgroundColor: '#FFFFFF', borderBottomColor: '#AAAAAA', borderBottomWidth: 2, padding: 5 }, cover: { flex: 1, height: 150, resizeMode: 'contain' }, info: { flex: 3, alignItems: 'flex-end', flexDirection: 'column', alignSelf: 'center', padding: 20 }, author: { fontSize: 18 }, title: { fontSize: 18, fontWeight: 'bold' } }); //他のファイルから読み込めるようにexportします。 module.exports = BookItem;
完成するとこんな感じで表示されます。
一応期待通りの動作をするようになりましたが、カテゴリの判定が厳しいので、入力欄ではなく、セレクトボックスにするといった工夫をしても良かったかもしれません。