[RN]ReactNative(React-Redux)を始めよう！

## はじめに
久々のブログ更新です。
GAUSSの[Tsunaki](https://twitter.com/tsunaki00)です。


[twitter](https://twitter.com/tsunaki00)やってます。フォローお願いします。
普段はAI競馬予想[SIVA](http://siva-ai.com/)を作ってます。



いまさらですが、アプリ開発に手軽で簡単なReactNativeを始めよう！ということで簡素化して書けるようにフレームワーク化を検討してみたので、公開したいと思います！

今回のフレームワークの目的は「簡素化」です。プログラムを簡単にしてラーニングを容易にします。
複雑なことをやる前の前段ではおすすめできます。

今回のプログラム : https://github.com/tsunaki00/RN-todo


React-Reduxは仕組みの説明を読んで理解するよりはシンプルなプログラムしっかり覚えた方が早いです！
早くキャッチアップできます。

React-Reduxは以下のQiitaがわかりやすく説明しています。
https://qiita.com/mpyw/items/a816c6380219b1d5a3bf

### 開発の構成について
以下のパッケージを使って開発します。
[利用パッケージ]

```
react : 16.8.3
react-native : 0.59.9
redux : 4.0.1
react-redux : 7.1.0
redux-thunk : 2.3.0
react-navigation : 3.11.0
axios : 0.19.0
native-base : 2.12.1
```

Httpアクセス周りはaxios、ページ遷移はreact-navigationを使用。
デザイン周りはnative-baseで開発します。



また、React開発ではフォルダ構成には悩むことが多いかと思います。
今回はコンポーネントのパッケージが多いことやモバイルアプリケーションは全体が小さいのでドメイン指向にしようと思います。

ドメイン指向はコンポーネントの再利用などの汎化しにくいデメリットはありますが、Reduxで複雑になりやすい構成をシンプルにすることができます。

## 1.開発準備
### 1-1.開発全体像（Todoアプリ)
今回はTodo機能を使って説明します。
 git : https://github.com/tsunaki00/RN-todo

Todo機能
 ・一覧画面
 ・詳細画面
 ・登録画面
 ・削除機能
 ・更新機能（実装してみよう）

![全画面.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52867/f09f846b-6990-3034-6292-d885c3113ddd.png)


### 1-2.開発環境の準備
[git](https://github.com/tsunaki00/RN-todo)からプロジェクトをcloneします。

```shell
## プログラム取得
$ git clone https://github.com/tsunaki00/RN-todo
## バックエンドの起動
$ cd RN-todo/server
# dockerの準備（初回起動は時間がかかる）
$ docker-compose build
$ docker-compose up -d
#アプリケーションの起動
$ cd RN-todo/TodoApp
$ yarn install
$ yarn run ios

```

### 1-3.Todoアプリの準備
これで起動の確認ができます。
<img width="375" alt="Simulator Screen Shot - iPhone X - 2019-06-21 at 16.20.02.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52867/ee28b1f5-002f-bf92-b447-022a0512299b.png">


## 2.アプリケーションの説明

### 2-1.ディレクトリ構成
前述したようにシンプル構成にするためドメイン指向となっています。
下図の通り業務ドメインごとに画面のプログラム一式を入れてます。

![図1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52867/326f6321-01d0-a481-b9a2-f1694a9f7ec9.png)

### 2-2. プログラムの説明
以下の業務ロジック部の説明を行う。
 ・Action
 ・Component
 ・Container
 ・Reducer

なお、今回は簡素化するために、ContainerとReducerは開発を最小限にできるようにしてあります。


src/pages/todo/index/...
![スクリーンショット 2019-06-21 17.06.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52867/5258efbc-244e-1c14-68e0-7402b4831e13.png)


### 2-2-1. Container
ContainerはComponentとアクションやステートをコネクトする役割となっています。
省力化を図るためにここはテンプレートにしており、プログラムを組込必要はありません。
※ネーミングルールに合わせて配置だけしてください。

```TodoIndexContainer.js
import { connect } from 'react-redux';
import Component from './TodoIndexComponent';     // 変更 : Componentの呼び出し
import * as Action from './TodoIndexAction'; // Actionの呼び出し


function mapStateToProps(state) {
  return Object.assign({} ,state.TodoIndex); // Storeの設定
}

function mapDispatchToProps(dispatch) {
  let actions = {};
  let keys = Object.keys(Action);
  for (let key of keys) {
    if(typeof Action[key] == 'function') {
      actions[key] = (...args) => dispatch(Action[key](...args))
    }
  }
  return actions;
}
export default connect(mapStateToProps, mapDispatchToProps)(Component);
```

### 2-2-2. Component
ComponentはViewの役割となっています。
こちらはUIに合わせてマークアップで開発します。
(非同期処理などはActionにDispatchします）

this.props.xxx
にActionで定義したメソッドが入ってきますので、処理に応じて実行してください。
※ Action経由でstate更新を行う度にrenderメソッドが呼ばれます。
  なのでrender内でステート更新行う処理は気をつけてください。


```TodoIndexComponent.js
import React from 'react';

import { Alert, FlatList, TouchableOpacity, RefreshControl  } from 'react-native';
import { Container, View ,Text, List, ListItem, Body, Button } from 'native-base';

import Swipeout from 'react-native-swipeout';
import FontAwesome from 'react-native-vector-icons/FontAwesome';

// Components
import Header from '../../../components/header/HeaderComponent'
import Content from '../../../components/content/ContentComponent';
import styles from './styles';

/**
 * ToDo
 */
export default class TodoIndexComponent extends React.Component {

  componentWillMount() {
    this.props.navigation.addListener('didFocus', () => this._onFocus())
  }

  _onFocus() {
    this.props.getList();
  }

  _delete(item) {
    Alert.alert(
      '削除',
      `「${ item.title}」を削除します。\nよろしいですか？`,
      [
        { text: 'Cancel', style: 'cancel' },
        { text: 'OK', onPress: () => this.props.remove(item.id) }
      ],
      {cancelable: false},
    );
  }

  render() {
    return (
      <Container style={styles.container}>
        <Header 
          title={"Todoリスト"}
          isBack={false}
          navigation={this.props.navigation} 
          rightRender={e =>
            <Button transparent onPress={e => {
              this.props.navigation.navigate('TodoEntry')
            }}>
              <Text><FontAwesome name="plus" /> 新規登録</Text>
            </Button>
          }
          />
        <Content wait={this.props.wait} refreshControl={
          <RefreshControl
            refreshing={false}
            onRefresh={() => {} }
          />
          }>
          <List>
          {this.props.results && this.props.results.length > 0 ? 
            <FlatList
              data={this.props.results}
              renderItem={({ item }) => (
                <Swipeout style={ styles.listItem } right={[
                  {
                    backgroundColor: "#fff",
                    component : (
                      <TouchableOpacity onPress={e => {}}>
                        <View style={ styles.updateButton }>
                          <FontAwesome name="edit" style={ styles.updateButtonText } />
                        </View>
                      </TouchableOpacity>
                    )
                  } ,{
                  backgroundColor: "#fff",
                  component : (
                    <TouchableOpacity onPress={e => {
                      this._delete(item);
                    }}>
                      <View style={ styles.deleteButton }>
                        <FontAwesome name="trash-o" style={ styles.deleteButtonText } />
                      </View>
                    </TouchableOpacity>
                  )
                  }]}>
                  <TouchableOpacity onPress={e => { this.props.navigation.navigate('TodoDetail', { id : item.id }) } } style={styles.row}>
                    <View style={ styles.listView } >
                      <Text>{ item.title }</Text>
                    </View>
                    <View style={ styles.listViewIcon }>
                      <FontAwesome name={"chevron-right"} />
                    </View>
                  </TouchableOpacity>
                </Swipeout>
              )}
            /> : 
              <ListItem>
                <Body>
                  <Text>登録はありません</Text>
                </Body>
              </ListItem>
            }
          </List> 
        </Content>
        {/* 登録ボタン */}
        <TouchableOpacity
            onPress={e => { this.props.navigation.navigate('TodoEntry') } } style={ styles.entry }>
          <View>
            <FontAwesome name="edit" size={30} />
          </View>
        </TouchableOpacity>

      </Container>
    )
  }
}
```
### 2-2-3. Action
Actionは非同期処理の役割となっています。
単純化するためredux-thunkでActionに処理を記載するようにしてます。
※この辺はカルチャーの違いで気持ち悪く見えるかもしれません。。。）

前述に通り、Componentのthis.props.xxxにここのメソッドが入ってきます。
以下のプログラムの例
・this.props. getList()
・this.props. remove()

```TodoIndexAction.js
import axios from 'axios';
import config from '../../../../assets/config';
import {actionCreatorFactory} from '../../../common/ActionCreator';

const BASE_URL = `${config.apiServer.protocol}://${config.apiServer.host}:${config.apiServer.port}${config.apiServer.path}`;
export const ACTION_TYPE = {
  TODO_LIST : actionCreatorFactory("TODO_LIST"),
  TODO_DELETE : actionCreatorFactory("TODO_DELETE"),
};

/** ToDo List */
export const getList = () => (dispatch) => {  
  dispatch({ type: ACTION_TYPE.TODO_LIST.start, payload: { wait : true  } });
  axios.get(`${BASE_URL}/todo`).then((res) => {
    dispatch({ type: ACTION_TYPE.TODO_LIST.success, payload: { wait : false,  results : res.data } });
  });
}

/**  ToDo削除 */
export const remove = (id) => (dispatch) => {  
  dispatch({ type: ACTION_TYPE.TODO_DELETE.start, payload: { wait : true ,  results : []  } });
  axios.delete(`${BASE_URL}/todo/${id}`).then((res) => {
    dispatch({ type: ACTION_TYPE.TODO_DELETE.success, payload: { wait : false,  results : res.data } });
  });
}
```
### 2-2-4. Reducer
Reducerはstateを更新役割となっています。
省力化を図るためにここはテンプレートにしており、初期ステートの設定以外はプログラムの組込の必要はありません。
※ネーミングルールに合わせて配置だけしてください。

```TodoIndexReducer.js
const initialState = {}
export default TodoIndexReducer = (state = initialState, action) => {  
  switch (action.type) {    
    default:
      return Object.assign({}, state , action.payload);
  }
}
```
### 2-3. プログラムの実行

```
$ yarn run ios
```
<img width="375" alt="Simulator Screen Shot - iPhone X - 2019-06-21 at 17.45.21.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52867/1231a3db-6a35-b0a4-2a61-9db531a8e652.png">


これで動きました！

## 2-4. その他のプログラムについて
react-navigationなどの説明を省略してしまってるので、画面遷移や起動から画面が呼ばれるまでの流れが抜けているので簡単にプログラムが実行されてる順序を書きます。

index.js -> src/App.js -> src/RootNavigator.js -> pages/todo/index.js -> pages/todo/index/TodoIndexContainer.js -> pages/todo/index/TodoIndexComponent.js

の順で動いてますので、細かく見たい場合は上記の順序でプログラムを追ってみてください。


## 3. さいごに

今回は簡単な動かし方の説明になってしまい概念的なことは飛ばしてしまいましたが、本件の簡素なReact NativeのBaseを使うことでエンジニアのラーニングコストを大幅に下げることができました！

詳細はまた書きます。

[twitter](https://twitter.com/tsunaki00)やってます。フォローお願いします。