noki雑記

iOS、ときどきAndroid

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

PropsStateもどちらもコンポーネントを制御するためのデータ。
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インターナショナルが中心となって言語仕様を標準化。それがECMAScriptJavascriptECMAScriptに基いた言語の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

公式ドキュメントには使い方は載っていないものの、紹介されているライブラリがあったので、使ってみました。警告もでないので、今のところこれが一番いいかもしれない。
ドキュメント
他と違って、上記ドキュメントに書いてあるとおり、iOSAndroidそれぞれのプロジェクトに追加修正しなければなりません。
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 ReloadHot ReloadingLive Reload は保存時に自動で再起動してくれるし、Hot Reloadingは変更箇所(変更画面)のみ変わります。例えば、複数回遷移した後に表示される画面の修正では、Live Reloadだと最初の画面まで戻ってしまいます(再起動)が、Hot Reloadingだと更新箇所だけ変わるのでデバッグしやすいと思います。

イベントハンドラ内でthisが使えない

以下のコードだと、動きそうで動きませんでした。onPressメソッド内で参照しているthisundefinedになってしまいます。

<TouchableHighlight
  onPress = { this.onPress }>
  ...
</TouchableHighlight>
...
onPress() {
  console.log(this.message);
}

こちらの記事 に書いているが、イベントハンドラにはthisは自動でバインドされないようで、さらにjsのバージョンによっても違うらしい。解決策はいろいろあるようだが、とりあえず以下で動いた。