ReactNative をちょっと触ってみました
ReactNativeがいい感じ、というのを聞いたので触ってみました。 内容はちょっと古いですが、Javascriptもあまり触ったこと無いので、それも含めてほとんどメモです。
環境構築
公式ドキュメントに沿えばOK.
実行してみる
下記コマンドを実行することでアプリを動かすことができます。
$ react-native run-ios $ react-native run-android
ただ、Android はエミュレータを立ち上げた状態から始めないといけませんでした。エミュレータは Android Studio から起動するのが推奨されてるっぽいけれど、コマンドでやってみました。
$ $ANDROID_HOME/tools/emulator -avd エミュレータの名前
iOSもたまにシミュレータが起動しないことがあるので、その場合はシミュレータを起動した状態でコマンドを実行すればOK。
(ビルドは成功するが No devices are booted.
というエラーが表示されることがある。)
import, export
既存のライブラリや新規に作成したコンポーネントなどを読み込みたい場合は以下のようにする。
import React from 'react'; import Component1 from './src/component1';
import したファイル内に定義したあるクラスを呼びたい場合は方法が2つ。
export default
を使って定義することで、import時に自動で呼ばれるようになります。
export default class Component1 extends React.Component { render() { return ( <Text>Component 1</Text> ); } }
export default
を使用すると、1つのファイルに複数の、外から使用したいクラスを定義できなくなります。常にdefault
指定したクラスが呼ばれるため。
なので、使用したいクラスにexport
を付け、import時にクラスを指定する必要がある。
export class Component1 extends React.Component { ... } export class Component2 extends React.Component { ... }
import {Component1, Component2} from './src/components';
Props and State
Props
もState
もどちらもコンポーネントを制御するためのデータ。
Props
は固定された値を指し、変化する値はState
として保存します。
// text is Props <SampleView text="this is sample text." /> // showText is State constructor(props) { super(props); this.state = { showText: true }; setIntervar(() => { this.setState({ showText: !this.state.showText }); }, 1000); }
Strictモード
React Native の機能ではなくJavascriptの機能だが、Strictモード(厳格モード)というのがあります。 このモードでは的確なエラーチェックが行われるため、曖昧な実装がエラーとなります。 Strictモードにする場合はファイルに以下を追加します。
'use strict'
ES6 (ES2015)
ECMAScript とは言語仕様のこと。95年に公開されたJavascriptは、多くのブラウザが独自に拡張していったことで互換性が低くなったため、Ecmaインターナショナルが中心となって言語仕様を標準化。それがECMAScript。JavascriptはECMAScriptに基いた言語の1つであり、ActionScriptなどもその1つ。
Lifecycle
ネットワーク関連の実装をした時に、react native can only update a mounted or mounting component
というエラー(Warning)が出ました。通信開始時にIndicatorを表示し、通信完了時にIndicatorを非表示にするような実装でしたが、どうもIndicatorの準備が整う前にIndicatorを表示させようとしてエラーになっていたらしい。
ではコンポーネントの準備が整ったタイミングがいつなのかというと、componentDidMount()
が呼ばれたときでした。他にもcomponentWillUnmount()
などがよく使われるらしいです。各コンポーネントにcomponentDidMount()
を実装したところWarningは消え、期待通りに動作しました。
Navigation
iOSのNavigationBarを表示してPush遷移をしたい場合は、公式ドキュメントで react-navigation
を使うことがまず載っています。しかし、v1.0.0-beta.9
時点の react-navigation
を使用するとBackAndroid is deprecated. Please use BAckHandler instead.
という警告が表示されます。ということで現時点では、React Nativeに古くから存在する Navigator
を使おうとも思いましたが、Navigator
の公式ドキュメントが見当たらない。。。なのでググりながら実装するしかないです。
react-navigation
公式ドキュメントのやり方でいけました。 上述した警告が出るのでバージョンアップに期待。
Navigator
やろうと思ったら、Navigator が deprecated になっていました。react-native-deprecated-custom-components
というのをインストールしてimportすればいけるらしいが、名前からして入れたくないので却下。
結局、最新バージョンを使用した場合は、Navigation に関しては正常に動作するものはなかったです。バージョンアップに期待するか、いい感じに動作する下位バージョンを探すしかないようです。
// npm でインストールしたライブラリの確認 $ npm ls $ npm ls react-navigation
react-native-navigation
公式ドキュメントには使い方は載っていないものの、紹介されているライブラリがあったので、使ってみました。警告もでないので、今のところこれが一番いいかもしれない。
ドキュメント
他と違って、上記ドキュメントに書いてあるとおり、iOSとAndroidそれぞれのプロジェクトに追加修正しなければなりません。
react-navigation
とくらべて、リリースタグを見てみると、かなり頻繁に更新されているようなので、いまのところは良さそう。
setState() のエラー
State
は、リアクティブ的に動作するので便利ではあるのですが、画面を遷移しているとCan only update a mounted or mounting component.
という警告が出ます。state が解放されていないのか、Component が開放されていないのか、よくわからないがエラーとなってしまう。とりあえずの回避策として、良くない例ではあるが、フラグ管理することにしました。
constructor(props) { super(props); this.state = { ... }; this.isMounted = false; } componentDidMount() { this.isMounted = true; } componentWillUnmounted() { this.isMounted = false; }
デバッグ関連
コンソールログ出力
以下実行すると各プラットフォーム毎にログが出力されます。
$ react-native log-ios $ react-native log-android
Chrome Developer Tools
公式ドキュメント
上記だけでは画面遷移時にどんな値が渡っているかなどの情報が見れなかったが、Chrome Developer Tools を使うと出力されました。
iOSなら、シミュレータ上で Cmd + d
を押してデバッグメニューを表示し、Remote JS Debugging を選択。すると http://localhost:8081/debugger-ui
が開くので、そこで Developer Tool を開き、シミュレータ上で Cmd + r
でリロードすればOK。
自動更新
ソースコードの保存時にアプリを更新してくれる機能が2つあります。Live Reload
とHot Reloading
。
Live Reload
は保存時に自動で再起動してくれるし、Hot Reloading
は変更箇所(変更画面)のみ変わります。例えば、複数回遷移した後に表示される画面の修正では、Live Reload
だと最初の画面まで戻ってしまいます(再起動)が、Hot Reloading
だと更新箇所だけ変わるのでデバッグしやすいと思います。
イベントハンドラ内でthisが使えない
以下のコードだと、動きそうで動きませんでした。onPressメソッド内で参照しているthis
がundefined
になってしまいます。
<TouchableHighlight onPress = { this.onPress }> ... </TouchableHighlight> ... onPress() { console.log(this.message); }
こちらの記事 に書いているが、イベントハンドラにはthisは自動でバインドされないようで、さらにjsのバージョンによっても違うらしい。解決策はいろいろあるようだが、とりあえず以下で動いた。