[モバイルアプリ] React NativeでiOS & Android両対応アプリを作る(4)

株式会社プライムストラクチャーの若手エンジニアのSayaです。

前回に引き続き、公式のチュートリアルを参考にしながらReact Nativeを用いたモバイルアプリの開発を行なっていきたいと思います。

ScrollViewの使い方

ScrollViewは複数のコンポーネントをスクロールで表示できます。
ScrollViewのすべての要素とビューは、現在スクリーンに表示されていなくてもレンダリングされます。
デフォルトは垂直方向のスクロールですが、horizontalプロパティを設定することで水平方向にもスクロールさせることが出来ます。

以下の例は画像とテキストが含まれた垂直方向のScrollViewです。

    import React, { Component } from 'react';
    import { AppRegistry, ScrollView, Image, Text } from 'react-native';

    export default class IScrolledDownAndWhatHappenedNextShockedMe extends Component {
      render() {
          return (
            <ScrollView>
              <Text style={{fontSize:96}}>Scroll me plz</Text>
              <Image source={{uri: "https://facebook.github.io/react-native/img/favicon.png", width: 64, height: 64}} />
              <Image source={{uri: "https://facebook.github.io/react-native/img/favicon.png", width: 64, height: 64}} />
              <Image source={{uri: "https://facebook.github.io/react-native/img/favicon.png", width: 64, height: 64}} />
              <Image source={{uri: "https://facebook.github.io/react-native/img/favicon.png", width: 64, height: 64}} />
              <Image source={{uri: "https://facebook.github.io/react-native/img/favicon.png", width: 64, height: 64}} />
              <Text style={{fontSize:96}}>If you like</Text>
              <Image source={{uri: "https://facebook.github.io/react-native/img/favicon.png", width: 64, height: 64}} />
              <Image source={{uri: "https://facebook.github.io/react-native/img/favicon.png", width: 64, height: 64}} />
              <Image source={{uri: "https://facebook.github.io/react-native/img/favicon.png", width: 64, height: 64}} />
              <Image source={{uri: "https://facebook.github.io/react-native/img/favicon.png", width: 64, height: 64}} />
              <Image source={{uri: "https://facebook.github.io/react-native/img/favicon.png", width: 64, height: 64}} />
              <Text style={{fontSize:96}}>Scrolling down</Text>
              <Image source={{uri: "https://facebook.github.io/react-native/img/favicon.png", width: 64, height: 64}} />
              <Image source={{uri: "https://facebook.github.io/react-native/img/favicon.png", width: 64, height: 64}} />
              <Image source={{uri: "https://facebook.github.io/react-native/img/favicon.png", width: 64, height: 64}} />
              <Image source={{uri: "https://facebook.github.io/react-native/img/favicon.png", width: 64, height: 64}} />
              <Image source={{uri: "https://facebook.github.io/react-native/img/favicon.png", width: 64, height: 64}} />
              <Text style={{fontSize:96}}>What's the best</Text>
              <Image source={{uri: "https://facebook.github.io/react-native/img/favicon.png", width: 64, height: 64}} />
              <Image source={{uri: "https://facebook.github.io/react-native/img/favicon.png", width: 64, height: 64}} />
              <Image source={{uri: "https://facebook.github.io/react-native/img/favicon.png", width: 64, height: 64}} />
              <Image source={{uri: "https://facebook.github.io/react-native/img/favicon.png", width: 64, height: 64}} />
              <Image source={{uri: "https://facebook.github.io/react-native/img/favicon.png", width: 64, height: 64}} />
              <Text style={{fontSize:96}}>Framework around?</Text>
              <Image source={{uri: "https://facebook.github.io/react-native/img/favicon.png", width: 64, height: 64}} />
              <Image source={{uri: "https://facebook.github.io/react-native/img/favicon.png", width: 64, height: 64}} />
              <Image source={{uri: "https://facebook.github.io/react-native/img/favicon.png", width: 64, height: 64}} />
              <Image source={{uri: "https://facebook.github.io/react-native/img/favicon.png", width: 64, height: 64}} />
              <Image source={{uri: "https://facebook.github.io/react-native/img/favicon.png", width: 64, height: 64}} />
              <Text style={{fontSize:80}}>React Native</Text>
            </ScrollView>
        );
      }
    }

    // skip these lines if using Create React Native App
    AppRegistry.registerComponent(
      'AwesomeProject',
      () => IScrolledDownAndWhatHappenedNextShockedMe);

これを実行すると画面スクロールを出来ることが確認できます。

またpagingEnabledプロップを用いることでスワイプで画面を見ることが出来るようになります。

ユーザーにコンテンツをズームさせたい場合には、maximumZoomScaleminimumZoomScaleを用います。
これによってズームインまたはズームアウトが使えるようになります。

List Viewsの使い方

データのリストを表示するList Viewでは一般にFlatListSectionListがよく使われます。
一般的な使い方として、サーバーから受信したデータを表示することが挙げられます。

FlatList

FlatListは一般的なScrollViewとは異なり、現在画面上に表示されている要素のみをレンダリングします。
全ての要素を一度にレンダリングする訳ではないので、時間の経過と共にアイテム数が変化する可能性のある長いデータリストに対して上手く機能します。

data : FlatListの情報源が入ります。
renderItem : dataから項目を1つ取り、レンダリングをする為の書式付きコンポーネントを返します。

    import React, { Component } from 'react';
    import { AppRegistry, FlatList, StyleSheet, Text, View } from 'react-native';

    export default class FlatListBasics extends Component {
      render() {
        return (
          <View style={styles.container}>
            <FlatList
              data={[
                {key: 'Devin'},
                {key: 'Jackson'},
                {key: 'James'},
                {key: 'Joel'},
                {key: 'John'},
                {key: 'Jillian'},
                {key: 'Jimmy'},
                {key: 'Julie'},
              ]}
              renderItem={({item}) => <Text style={styles.item}>{item.key}</Text>}
            />
          </View>
        );
      }
    }

    const styles = StyleSheet.create({
      container: {
       flex: 1,
       paddingTop: 22
      },
      item: {
        padding: 10,
        fontSize: 18,
        height: 44,
      },
    })

    // skip this line if using Create React Native App
    AppRegistry.registerComponent('AwesomeProject', () => FlatListBasics);

上の画像のようにdata内のKeyに入れられた名前データがテキストでレンダリングされます。

SectionList

SectionListによってデータをグループ分けして表示することが出来ます。

    import React, { Component } from 'react';
    import { AppRegistry, SectionList, StyleSheet, Text, View } from 'react-native';

    export default class SectionListBasics extends Component {
      render() {
        return (
          <View style={styles.container}>
            <SectionList
              sections={[
                {title: 'D', data: ['Devin']},
                {title: 'J', data: ['Jackson', 'James', 'Jillian', 'Jimmy', 'Joel', 'John', 'Julie']},
              ]}
              renderItem={({item}) => <Text style={styles.item}>{item}</Text>}
              renderSectionHeader={({section}) => <Text style={styles.sectionHeader}>{section.title}</Text>}
              keyExtractor={(item, index) => index}
            />
          </View>
        );
      }
    }

    const styles = StyleSheet.create({
      container: {
       flex: 1,
       paddingTop: 22
      },
      sectionHeader: {
        paddingTop: 2,
        paddingLeft: 10,
        paddingRight: 10,
        paddingBottom: 2,
        fontSize: 14,
        fontWeight: 'bold',
        backgroundColor: 'rgba(247,247,247,1.0)',
      },
      item: {
        padding: 10,
        fontSize: 18,
        height: 44,
      },
    })

    // skip this line if using Create React Native App
    AppRegistry.registerComponent('AwesomeProject', () => SectionListBasics);

フェッチを使う

React Nativeは、ネットワーキングのニーズに対応したFetch APIを提供しています。
詳細については、MDNのガイドのUsing Fetchを参照してください。

リクエストをする

任意のURLからコンテンツを取得するには、以下のようにフェッチするURLを渡します。

fetch('https://mywebsite.com/mydata.json');

また、HTTP要求をカスタマイズするためのオプションの2番目の引数も取得します。追加のヘッダーを指定したり、POSTリクエストを作成することができます。

    fetch('https://mywebsite.com/endpoint/', {
      method: 'POST',
      headers: {
        Accept: 'application/json',
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        firstParam: 'yourValue',
        secondParam: 'yourOtherValue',
      }),
    });

レスポンスの処理

FetchメソッドはPromiseを返します。
これにより、非同期で動作するコードを簡単に記述できます。

    function getMoviesFromApiAsync() {
      return fetch('https://facebook.github.io/react-native/movies.json')
        .then((response) => response.json())
        .then((responseJson) => {
          return responseJson.movies;
        })
        .catch((error) => {
          console.error(error);
        });
    }

また、async / await構文を使用することもできます。

    async function getMoviesFromApi() {
      try {
        let response = await fetch(
          'https://facebook.github.io/react-native/movies.json',
        );
        let responseJson = await response.json();
        return responseJson.movies;
      } catch (error) {
        console.error(error);
      }
    }

フェッチによって生じる可能性のあるエラーをキャッチすることを忘れないでください。

    import React from 'react';
    import { FlatList, ActivityIndicator, Text, View  } from 'react-native';

    export default class FetchExample extends React.Component {

      constructor(props){
        super(props);
        this.state ={ isLoading: true}
      }

      componentDidMount(){
        return fetch('https://facebook.github.io/react-native/movies.json')
          .then((response) => response.json())
          .then((responseJson) => {

            this.setState({
              isLoading: false,
              dataSource: responseJson.movies,
            }, function(){

            });

          })
          .catch((error) =>{
            console.error(error);
          });
      }



      render(){

        if(this.state.isLoading){
          return(
            <View style={{flex: 1, padding: 20}}>
              <ActivityIndicator/>
            </View>
          )
        }

        return(
          <View style={{flex: 1, paddingTop:20}}>
            <FlatList
              data={this.state.dataSource}
              renderItem={({item}) => <Text>{item.title}, {item.releaseYear}</Text>}
              keyExtractor={({id}, index) => id}
            />
          </View>
        );
      }
    }

他のネットワークライブラリの使用

XMLHttpRequest APIもReact Nativeに組み込まれています。
つまり、それに依存するfrisbeeやaxiosなどのサードパーティのライブラリを使用することができます。
また、必要に応じてXMLHttpRequest APIを直接使用することもできます。

    var request = new XMLHttpRequest();
    request.onreadystatechange = (e) => {
      if (request.readyState !== 4) {
        return;
      }

      if (request.status === 200) {
        console.log('success', request.responseText);
      } else {
        console.warn('error');
      }
    };

    request.open('GET', 'https://mywebsite.com/endpoint/');
    request.send();

XMLHttpRequestのセキュリティモデルは、ネイティブアプリにCORSの概念がないためWebとは異なります。

WebSocketのサポート

React Nativeは、双方向通信であるWebSocketsもサポートしています。

    var ws = new WebSocket('ws://host.com/path');

    ws.onopen = () => {
      // connection opened
      ws.send('something'); // send a message
    };

    ws.onmessage = (e) => {
      // a message was received
      console.log(e.data);
    };

    ws.onerror = (e) => {
      // an error occurred
      console.log(e.message);
    };

    ws.onclose = (e) => {
      // connection closed
      console.log(e.code, e.reason);
    };

まとめ

ここまででReact Nativeの一通りの機能を確認してきました。
JavaScriptによって手軽に見た目部分を作っていけることが分かりました。
次回からは実際にアプリケーションを作ってみたいと思います。

参考文献

Using a ScrollView · React Native
Using List Views · React Native
Networking · React Native