expoによるReact Nativeのおさらい（最低限）


しばらく使ってないと0ベースで忘れてしますので、触りだけメモ。
環境はMac想定です。すみません。

## 前提知識

* React NativeはReactを使ってクロスプラットフォーム(iOS, Android)開発を行うためのしくみ
* 開発ツールとしてはexpoか、react-native-cliのいずれかを利用するのが一般的
* expoは簡単だが限られた範囲での開発となる。react-native-cliは制限は無くどんなニーズにも対応
* ただ多くの場合expoの範囲で事足りることが多いのでここではexpoを利用する
* なお、ejectすることでexpoの制限を超えることができる（もちろん失うものもあるが）

## 環境構築

* node.jsをインストールしておく（必須）
* watchmanをインストールしておく
* yarnをインストールしておく
* XcodeとAndroid Studioもインストールしておいたほうがいい
  * Android Studioではエミュレータを作成し、起動できるようにしておく



## expoのインストール

npmでglobalにインストールします。

```bash
npm install -g expo-cli
```

## 作業場＋雛形作成

initコマンドでプロジェクトを作成します。
下記の場合、expo_sampleというフォルダが作成され、その中に雛形が生成されます。

```bash
expo init expo_sample
```

いくつか質問をしてきます。
まずは、テンプレートの選択。まあ、blankでよいかと。

```bash
? Choose a template: (Use arrow keys)
  ----- Managed workflow -----
❯ blank         minimal dependencies to run and an empty root component
  tabs          several example screens and tabs using react-navigation
  ----- Bare workflow -----
  bare-minimum  minimal setup for using unimodules
```

次にhome screenに出すapp名を入れろ！と言われるので、まあ、プロジェクトと同じexpo_sampleとでもしておきます。

```bash
? Choose a template: expo-template-blank
? Please enter a few initial configuration values.
  Read more: https://docs.expo.io/versions/latest/workflow/configuration/ › 50% completed
 {
   "expo": {
     "name": "<The name of your app visible on the home screen>",
     "slug": "expo_sample"
   }
 }
```

Yarnが見つかればYarnを使うか？と聞かれます。とりあえずYesで。

```bash
? Yarn v1.13.0 found. Use Yarn to install dependencies? (Y/n)
```

### ディレクトリ構造

上記の操作が完了すると、下記のような雛形構造が生成されます。
App.jsをいじって開発することになります。

![スクリーンショット 2019-06-04 4.37.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/55188/4553113b-3122-b8fa-2df8-5674f6c80db0.png "スクリーンショット 2019-06-04 4.37.38.png")


### 動作確認

何かコーディングする前に、初期状態でうまく動作するか確認しておくといいでしょう。
プロジェクトフォルダにおいて下記コマンドを実行します。

```bash
expo start
```

するとブラウザが起動し、下記の画面が起動します。

![スクリーンショット 2019-06-04 4.42.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/55188/4655e2ee-dd27-05b0-8345-14673e79407f.png "スクリーンショット 2019-06-04 4.42.30.png")

この画面を利用して実機によるテスト、エミュレータによるテストが可能です。

#### 実機によるテスト

実機でテストをしたい場合はiOSもしくはAndroidでexpoアプリをインストールして、アプリを利用してQRコードを読み取ることでテストできます。

>iOSではカメラでQR読めば「アプリを起動するか？」聞いてきます。

#### エミュレータによるテスト

上記ブラウザ画面の左メニューで「Run on Android device/emulator」もしくは「Run on iOS simulator」を選びます。ここではiOSシミュレータを利用してみます。

下記のようなものが表示されればOKです。

![スクリーンショット 2019-06-04 4.49.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/55188/b7394eec-df64-0af5-7c38-2dc8b48c41e6.png "スクリーンショット 2019-06-04 4.49.26.png")




## 最低限の実装：Styleをインラインに記述

サンプルコードが長くなるので、外部設定されたSytleをインラインに記述してみます。

```diff:App.js
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';

export default class App extends React.Component {
  render() {
    return (
+      <View style={{flex:1, paddingVertical:80, alignItems:'center'}}>
        <Text>Open up App.js to start working on your app!</Text>
      </View>
    );
  }
}

-const styles = StyleSheet.create({
-  container: {
-    flex: 1,
-    backgroundColor: '#fff',
-    alignItems: 'center',
-    justifyContent: 'center',
-  },
});
```

## 最低限の実装：Buttonを配置。クリックしてアラート表示（ついでにlog出力）

何は無くともボタンは必要。onPressに関数を設定することでその内容が実行されます。
まずはクロージャを利用。

```diff:App.js
import React from 'react';
+//Buttonを追加
+import { StyleSheet, Text, View, Button } from 'react-native';

export default class App extends React.Component {
  render() {
    return (
      <View style={{flex:1, paddingVertical:80, alignItems:'center'}}>
        <Text>Open up App.js to start working on your app!</Text>
+        <Button
+          title="Click Me"
+          onPress={()=>{
+            alert("アラート表示");
+            console.log("コンソールログ")
+          }}
+        />
      </View>
    );
  }
}
```

実行すると、下記のような感じ。

![スクリーンショット 2019-06-04 5.07.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/55188/536c2ef1-fbd1-5c67-7bea-0d9fed3b5d0c.png "スクリーンショット 2019-06-04 5.07.32.png")

>console.log()はexpo startさせたコンソールに表示されているはずです。


なお、外部関数化することもできます。その場合は、this.functionで呼び出し。

```diff:App.js
import React from 'react';
//Buttonを追加
import { StyleSheet, Text, View, Button } from 'react-native';

export default class App extends React.Component {
  render() {
    return (
      <View style={{flex:1, paddingVertical:80, alignItems:'center'}}>
        <Text>Open up App.js to start working on your app!</Text>
        <Button
          title="Click Me"
+          onPress={this.showAlert}
        />
      </View>
    );
  }

+  //アラート表示
+  showAlert = () => {
+    alert("アラート表示");
+    console.log("コンソールログ");
+  }

}
```

## 最低限の実装：変数（state）の利用

Reactにはstateとpropsという2つの変数を扱う仕組みがあります。まずは、state。
あるリアクトコンポーネント（ここではApp.js）内で利用する変数はすべてstateにおいて定義されている必要があります。
また、変数の更新は

```
name = "hoge";
```

というような形はとれず、

```
this.setState({name: "hoge"});
```

というような形でsetState()を利用して更新する必要があります。

>なお、１つのアプリにApp.js, A.js, B.jsと複数のコンポーネント（画面）が存在する場合、原則としてコンポーネント間で値を共有することができないため、別途Redux等のしくみを利用する必要があります。


以下、サンプルです。
stateにname, ageを表示し、初期値としてhoge, 33を定義しています。
ボタンをクリックしたら、名前がfooに切り替わるというものです。

```diff:App.js
import React from 'react';
import { StyleSheet, Text, View, Button } from 'react-native';

export default class App extends React.Component {

+  //stateを定義
+  state = {
+    name: "hoge",
+    age: 33
+  }

  render() {
    return (
      <View style={{flex:1, paddingVertical:80, alignItems:'center'}}>
+        <Text>こんにちは。{this.state.name}さん。</Text>
        <Button
          title="Change Name"
+          onPress={this.changeName}
        />
      </View>
    );
  }

+  //name変更
+  changeName = () => {
+    this.setState({
*      name: "Foo"
+    });
+  }

}
```

動作サンプル。

![スクリーンショット 2019-06-04 5.28.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/55188/4edf1633-0486-4a02-4b40-cba5f610b526.png "スクリーンショット 2019-06-04 5.28.40.png")


## 最低限の実装：変数（Props）の利用

propsはタグコンポーネント間で値を受け渡す仕組みです。
コードを見たほうが早いでしょう。<Hello/>という関数によるカスタムタグを定義し、カスタムタグに対しpropsで初期値を渡しています。

```diff:App.js
import React from 'react';
import { StyleSheet, Text, View, Button } from 'react-native';

export default class App extends React.Component {

  //stateを定義
  state = {
    name: "hoge",
    age: 33
  }

  render() {
    return (
      <View style={{flex:1, paddingVertical:80, alignItems:'center'}}>
        <Text>こんにちは。{this.state.name}さん。</Text>
        <Button
          title="Change Name"
          onPress={this.changeName}
        />
+        <Hello to="Bob"/>
+        <Hello to="Tom"/>
      </View>
    );
  }
  //アラート表示
  changeName = () => {
    this.setState({
      name: "Foo"
    });
  }
}

+function Hello(props){
+  return (
+    <Text>Hello. {props.to}!</Text>
+  );

}
```

実行サンプル

![スクリーンショット 2019-06-04 5.37.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/55188/7f6506b2-0353-20de-4d7f-2ac9c284c6a9.png "スクリーンショット 2019-06-04 5.37.24.png")


## 最低限の処理：Input処理

React(Native)で「え？」と思わされるのがForm等でのInput処理です。
結論から言えば、TextInput等の入力系コンポーネントを配置しただけでは値が変更できません。
下記コードはstateで初期値text1を配置し、それをInputTextの初期値として設定しているコードです。
下記コードを実行して、テキストを編集してみてください。できないはずです。

```diff:App.js
import React from 'react';
+//TextInputを追加
+import { StyleSheet, Text, View, Button, TextInput } from 'react-native';

export default class App extends React.Component {

  //stateを定義
  state = {
+    text1 : "placeholder"
  }

  render() {
    return (
      <View style={{flex:1, paddingVertical:80, alignItems:'center'}}>
        <Text>入力してください</Text>
+        <TextInput
+          value={this.state.text1}
+        />
        <Button
          title="Entry"
          onPress={this.entry}
        />
      </View>
    );
  }
  //TextInputの内容をアラートで表示
  entry = () => {
    alert(this.state.text1);
  }
}
```

実行サンプル

![スクリーンショット 2019-06-04 5.50.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/55188/41f8e4a0-9dd0-02d5-3978-f009e647ef34.png "スクリーンショット 2019-06-04 5.50.23.png")


編集可能とするためには、onChangeText()を定義して、テキストが変更される度、stateを更新する処理が必要です。
この辺を簡略化してくれるコンポーネントもありますが、これが基本となります。

```diff:App.js
import React from 'react';
import { StyleSheet, Text, View, Button, TextInput } from 'react-native';

export default class App extends React.Component {

  //stateを定義
  state = {
    text1 : "placeholder"
  }

  render() {
    return (
      <View style={{flex:1, paddingVertical:80, alignItems:'center'}}>
        <Text>入力してください</Text>
        <TextInput
          value={this.state.text1}
+          onChangeText = {(t) => this.setState({text1:t})}
        />
        <Button
          title="Entry"
          onPress={this.entry}
        />
      </View>
    );
  }
  //アラート表示
  entry = () => {
    alert(this.state.text1);
  }
}
```

実行サンプル

![スクリーンショット 2019-06-04 5.58.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/55188/66786495-e822-e10e-46d8-dd43eaecb70c.png "スクリーンショット 2019-06-04 5.58.12.png")

## 最低限の実装：カッコいいコンポーネント(react-native-elements)を使う

```bash
npm install react-native-elements
```

なぜか再度npm installしないと使えません。。。

```bash
npm install
```

>yarn add react-native-elementsだと、1回でOKみたい。

そして実装。上と同じ機能をreact-native-elementsのコンポーネントで置き換えてみました。

>バージョンアップしてFormInput, FormLabel等はInputに統合されたみたい。。。

```js:App.js
import React from 'react';
//Buttonはかぶるの消す
import { StyleSheet, Text, View, TextInput } from 'react-native';
//使うコンポーネントをimport
import { Card, Button, Input} from 'react-native-elements';

export default class App extends React.Component {

  //stateを定義
  state = {
    text1 : "placeholder"
  }

  render() {
    return (
      <View style={{flex:1, paddingVertical:80}}>
        <Card title="入力してね">
          <Input
            value={this.state.text1}
            label="text1"
            errorMessage="error"
            onChangeText={(t)=>{this.setState({text1:t})}}
          />
          <Button
            title="Entry"
            buttonStyle={{marginTop:30}}
            borderRadius={20}
            onPress={this.entry}
          />
        </Card>
      </View>
    );
  }
  //アラート表示
  entry = () => {
    alert(this.state.text1);
  }
}
```

実行サンプル

![スクリーンショット 2019-06-04 6.30.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/55188/4fbe9935-1a1f-9571-d0b2-7381070da54b.png "スクリーンショット 2019-06-04 6.30.01.png")


まだいろいろあるけど、必要に応じて随時追加します。