React.jsを使ってネイティブ風UIのモバイルアプリを作れる「Reapp」。GoogleマップとFirebaseを使った簡単な地図アプリを作ってみました。
2016年5月にこの投稿を更新し、ReactおよびReappの変更を反映しました。
Reactはユーザーインターフェイスを作成するためのJavaScriptライブラリーです。Facebookが開発し、メンテナンスされてきたことで人気を集めています。
なぜReactなのか
Reactが他のJavaScriptライブラリーと異なるのは、「仮想DOM」のコンセプトに基づいていることです。変更が加えられると、実際のDOMを更新するのではなく、仮想DOMを更新します。仮想DOMに変更がある場合、DOMを何度も更新するのではなく、1度だけ更新します。
以下は公式サイトからの引用です。
Reactを利用するとDOMについて考える必要性がなくなり、よりシンプルなプログラミングモデルとより良いパフォーマンスが得られます。また、ReactはNodeを利用してサーバー上でレンダリングもでき、さらにReact Nativeを使えばネイティブアプリにも利用できます。
Reapp.ioの紹介
Reappは、モバイルアプリを制作するプラットホームです。Reappはモバイルアプリに最適化されたUIキットのコンポーネントを提供し、カスタマイズしてモバイルアプリを作れます。
制作するもの
この記事では、Reappを使用したモバイルアプリの制作方法を説明します。ReappとGoogle マップのAPIを使えば、ユーザーはさまざまな場所にデータを保存しやすくなります。データの保存には、Firebaseをバックエンドとして利用します。
サンプルのソースコードは、GitHubで入手してください。
はじめに
reappをインストールし、ReactAppと呼ばれるプロジェクトを作成します。
npm install -g reapp
reapp new ReactApp
プロジェクトのディレクトリを開き、reappを実行すれば、アプリはhttp://localhost:3010で実行されるはずです。
cd ReactApp && reapp run
以下は、プロジェクト構造です。
プロジェクトディレクトリの中にapp.jsファイルを含むアプリフォルダがあります。app.jsファイルはアプリケーションごとに異なる各ルートを定義します。componentsフォルダーには特定のルートを要求した場合にレンダリングされる各コンポーネントが含まれています。
ビューの作成
components/homeフォルダーからsub.jsxファイルを削除します。どのように機能するかを理解するために、home.jsxを開き、既存のコードを削除して真っ白な状態から始めましょう。コンポーネントをレンダリングするため、Homeという名のクラスを作成します。
import { Reapp, React, View} from 'reapp-kit';
var Home = React.createClass({
render: function() {
return (
<h1>Welcome to Reapp!!</h1>
);
}
});
export default Reapp(Home);
render関数が表示するビューを返します。次にapp.jsファイルのルートを更新します。
import './theme';
import { router, route } from 'reapp-kit';
router(require,
route('home', '/')
);
変更を保存し、サーバーを再開します。ブラウザーでhttp://localhost:3010を開くと初期設定のビューが表示されるはずです。モバイルアプリとして見られるように、Chromeデベロッパーツールのデバイスエミュレーションを有効にすることをおすすめします。
次に、Google マップをビューに組み込みます。home.jsxがrender関数内のビューを返すように修正し、アプリのヘッダーを追加します。
<View title="Where Am I">
</View>
Google マップを表示するために、Google マップのAPI参照をassets/web/index.htmlに追加し、新しいマップコンポーネントを作成します。
<script src="https://maps.googleapis.com/maps/api/js?v=3.exp"></script>
home.jsxで、Google マップを表示する新しいReactコンポーネントを作成します。
var MapView = React.createClass({
render: function() {
return (
<div id="map"><span>Map Would be Here !!</span></div>
);
}
});
MapViewコンポーネントをホームビューに追加します。
<View title="Where Am I">
<MapView />
</View>
以下のスタイルをassets/web/index.htmlに追加します。
<style>
#map {
width: 100%;
height: 400px;
margin: 0px;
padding: 0px;
}
</style>
変更を保存し、サーバーを再開します。アプリの画面に「Map Would be Here !!」というテキストが表示されます。
Google マップを追加する
Reactコンポーネントをネストする方法は分かったので、次はMapViewのrender関数内にあるspanを削除し、実際のマップに置き換えます。コンポーネントを実装したら、コンポーネントはGoogle マップを作成し、#mapのdivの中でレンダリングします。
componentWillMountライフサイクルメソッドにGoogle マップのコードを書きます。MapViewコンポーネントの中にcomponentWillMountメソッドを追加してください。
componentWillMount: function() {
// Code will be here
},
componentDidMountの中にデフォルトのマップ位置やマップオプションを定義し、マップを作成します。
var sitepoint = new google.maps.LatLng(-37.805723, 144.985360);
var mapOptions = {
zoom: 3,
center: sitepoint
},
map = new google.maps.Map(ReactDOM.findDOMNode(this), mapOptions);
this.setState({
map: map
});
ReactDOM.findDOMNodeを使うにはreact-domが必要なので、冒頭でreact-domをインポートする必要があります。
var ReactDOM = require('react-dom')
このコードでは、ReactDOM.findDOMNodeは、コンポーネントのDOMノード要素を参照し、setStateがUIの更新をトリガーします。変更を保存し、サーバーを再開します。うまくいけば、マップが表示されるはずです。
次に、Google マップにマーカーを加えましょう。animationやdraggableのようなオプションを設定できます。
marker = new google.maps.Marker({
map:map,
draggable:true,
animation: google.maps.Animation.DROP,
position: sitepoint
});
MapViewコンポーネント全体は以下のとおりです。
var MapView = React.createClass({
componentDidMount: function() {
var sitepoint = new google.maps.LatLng(-37.805723, 144.985360);
var mapOptions = {
zoom: 3,
center: sitepoint
},
map = new google.maps.Map(ReactDOM.findDOMNode(this), mapOptions);
marker = new google.maps.Marker({
map:map,
draggable:true,
animation: google.maps.Animation.DROP,
position: sitepoint
});
this.setState({
map: map
});
},
render: function() {
return (
<div id="map"><span>Map Would be Here !!</span></div>
);
}
});
変更を保存し、サーバーを再開すればマーカー付きのマップができあがります。
位置情報の追加
ユーザーがマーカーをドラッグすると、位置情報が表示されるべきです。位置情報を表示するには、Homeコンポーネントの中に必要なHTMLを追加します。render関数のコードを変更し、以下のように設定してください。
render: function() {
return (
<View title="Where Am I">
<MapView />
<div style={{width:100 + '%',height:100 + 'px',margin: 0 + ' auto',padding:10 + 'px'}} id="infoPanel">
<div>
<span><b>Position:</b></span>
<span id="info"></span>
</div>
<div>
<span><b>Address:</b></span>
<span id="address"></span>
</div>
</div>
</View>
);
}
次にデフォルトの位置(縦方向と横方向の座標)や住所をハードコードする必要があります。componentDidMountメソッドの中のsitepoint変数を初期化してから、次の行を追加します。
document.getElementById('info').innerHTML = '-37.805723, 144.985360';
アドレスを表示するにはGoogle Maps Geocoderを利用します。
geocoder.geocode({
latLng: marker.getPosition()
}, function(responses) {
if (responses && responses.length > 0) {
document.getElementById('address').innerHTML = responses[0].formatted_address;
}
});
現時点のMapViewコンポーネントは以下のようになります。
var MapView = React.createClass({
componentDidMount: function() {
var geocoder = new google.maps.Geocoder();
var sitepoint = new google.maps.LatLng(-37.805723, 144.985360);
document.getElementById('info').innerHTML = '-37.805723, 144.985360';
var mapOptions = {
zoom: 3,
center: sitepoint
},
map = new google.maps.Map(ReactDOM.findDOMNode(this), mapOptions),
marker = new google.maps.Marker({
map:map,
draggable:true,
animation: google.maps.Animation.DROP,
position: sitepoint
});
geocoder.geocode({
latLng: marker.getPosition()
}, function(responses) {
if (responses && responses.length > 0) {
document.getElementById('address').innerHTML = responses[0].formatted_address;
}
});
this.setState({
map: map
});
},
render: function() {
return (
<div id="map"><span>Map Would be Here !!</span></div>
);
}
});
変更を保存し、サーバーを再開すればデフォルト位置と住所がアプリに表示されるはずです。
今度は、マーカーをドラッグしたら位置とアドレスを更新するため、dragendイベントリスナーを追加します。dragendコールバッグ機能でマーカーの位置とアドレスが取得され、addressとinfo要素の値が更新されます。
google.maps.event.addListener(marker, 'dragend', function(e) {
var obj = marker.getPosition();
document.getElementById('info').innerHTML = e.latLng;
map.panTo(marker.getPosition());
geocoder.geocode({
latLng: obj
}, function(responses) {
if (responses && responses.length > 0) {
document.getElementById('address').innerHTML = responses[0].formatted_address;
}
});
});
変更を保存し、サーバーを再開してください。これでマーカーのドラッグ、情報の更新は終わり、ドラッグは完了です。
Firebaseに情報を保存
Firebaseに座標を保存するためのボタンを追加します。プロジェクトにreapp-uiを追加します。
npm install reapp-ui@0.12.47
ボタンコンポーネントをHome.jsxにインポートします。
import Button from 'reapp-ui/components/Button';
<View>コンポーネントの下のHomeコンポーネントにボタンを追加します。Saveボタンをタップすると、座標がFirebaseに保存されます。
<Button onTap={this.savePosition}>Save </Button>
このアプリでFirebaseのサービスを利用するために、無料アカウントに登録します。登録すると、Firebase URLが提供され、利用を開始できます。以下が、私のFIrebase URLです。
https://blistering-heat-2473.firebaseio.com
Firebaseのアカウントにログインし、ダッシュボードに表示されているFirebase URLのプラスアイコンをクリックして以下のようなURLを作成してください。
https://blistering-heat-2473.firebaseio.com/Position
このURLを使って位置情報を保存します。
Firebaseへの参照をassets/web/index.htmlに入れます。
<script src="https://cdn.firebase.com/js/client/2.0.4/firebase.js"></script>
次に、保存ボタンをタップすると現れるHomeコンポーネントのsavePosition機能を定義します。
savePosition: function() {
var wishRef = new Firebase('https://blistering-heat-2473.firebaseio.com/Position');
var pos = document.getElementById('info').innerHTML;
var address = document.getElementById('address').innerHTML;
wishRef.push({
'Position': pos,
'Address': address
});
},
Firebase URLを利用してFirebaseオブジェクトを作成し、push API機能を使ってデータをFirebaseに入れます。
変更を保存し、サーバーを再開してください。マップ上の位置を特定し、保存をクリックします。FIrebaseをチェックするとデータが保存されるはずです。
データが問題なく保存されたことをユーザーに知らせるアラートを追加します。これにはモーダルコンポーネントを使うので、モーダルをHome.jsxにインポートします。
import Modal from 'reapp-ui/components/Modal';
ホームビューコンポーネントのrender関数の内部では、<MapView />の上部に以下のモーダルコードを追加します。
{this.state.modal &&
<Modal
title="Coordinates Saved."
onClose={() => this.setState({ modal: false })}>
</Modal>
}
モーダルはstate.modalがtrueの場合に表示され、getInitialStateメソッドを使ってアプリがロード中のときはstate.modalを初期化してfalseにします。そして、Homeコンポーネントの中にgetInitialStateを定義してください。
getInitialState: function() {
return {
modal: false
};
}
データをFirebaseに入れ、savePositionメソッドの中でstate.modalをtrueに設定するとモーダルが表示されます。
this.setState({ modal: true });
変更を保存し、サーバーを再開してください。アプリをロードしたら、保存ボタンをクリックしてデータが保存されるとモーダルが表示されます。
最後に
記事ではでReactJS、Reapp、Firebaseを使ってモバイルアプリを作成する方法を解説しました。今回は、Google マップで選択した座標を保存するアプリを作りました。
この記事がReactJSを使ってモバイルアプリを制作するきっかけになればうれしいです。
(原文:Creating a Mobile Application with Reapp)
[翻訳:中村文也]
[編集:Livit]