【React Native】アプリ開発を楽にする、ボイラープレート更新したよ (expo 最新: SDK 35)


## 背景

一年前、こちらの[ボイラープレート](https://github.com/WataruMaeda/react-native-boilerplate)をひっそりリリースしていました。当時はまだReact Native始めたばかりで、非効率でわかりづらいコードもありましたが、それから1年JSスタックでの開発を経験し改めてこのボイラープレートを改善しようと決意。

## ボイラープレートとは

新しくReactNativeでアプリを作り始める時の、お決まりの構成。テンプレート的なものになります。本来、アプリ開発を始める時、プロジェクトのセットアップが必要になります。ファイル構造を変更し、必要なライブラリをインストールし、UIコンポーネントを作成する。これらにかかる時間は時に膨大で、特に初めてReact Nativeを触る人にとってはとても大変な作業になります。セットアップにかかる時間を無くし、あらかじめインストール、セットアップされた環境から始めることで、開発に集中できます。

## 含まれるもの

Redux, Navigation, FontAwsome, カスタムフォント、アイコン、画像のプレローディングのセットアプが完了しています。ボイラープレートのUIはベーシックなものになります。こちらは利用者がカスタマイズしやすいよう、必要最低限のセットアップのみにとどめています。

![demo.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/102821/0274c808-2898-adf9-8fc7-647cec224dd3.gif)

## セットアップ

5分もかかりません。

1. expoを持ってない方、[こちら](https://expo.io/learn)よりセットアップしてください。
2. [react-native-boilerplate](https://github.com/WataruMaeda/react-native-boilerplate)より、zipファイルをDLしてください。
3. `app.json`の name と slug 名を変更してください

```app.json
"name": "your-app-name",
"slug": "your-app-name",
```
4. yarn install もしくは npm install でプロジェクトをビルド
5. `expo start --ios`

これでiPhoneのシミュレータが立ち上がります。
あとはコードを書くのみです。

## ファイル構成

```
/src
  /components ... Button, Inputのような再利用可能な UI component 
     ∟ Button
  /modules ... Redux actions & state
  /routes
     ∟ /navigator ... ナビゲージョン関連
        ∟ drawer
        ∟ stack
        ∟ tab
     ∟ /subview ... ナビゲーションで使用するView
       ∟ sideMenu
  /scenes ... 画面一覧
     ∟ screen1
     ∟ screen2
  /styles ... アプリで使用するtheme, assets
     ∟ images
     ∟ fonts
     ∟ colors
  /utils
     ∟ store ... redux global state
     ∟ connector ... component と redux actions, stateをつなぐ
```

## Navigationのカスタマイズ

`/routes/navigator` 配下でDrawer, Stack, Tab ナビゲーションそれぞれ宣言がなされています。もしDrawerが必要なく、Tab, Stack ナビゲーションのみが必要な場合、[routes/routes.js](https://github.com/WataruMaeda/react-native-boilerplate/blob/master/src/routes/routes.js)のimport を `tab`に変更してください。

```/routes/routes.js
// import drawerNavigator from './navigator/drawer'
import tabNavigator from './navigator/tabs'
...

// const Router = createAppContainer(drawerNavigator)
const Router = createAppContainer(tabNavigator)
return <Router />
```
Stackナビゲーションのみが必要な場合も同様、import を `navigation/stack`へ変更してください。

## Redux

#### ComponentとReduxの接続

ある画面から、Redux store と dispatch関数を使用する際、[connector](https://github.com/WataruMaeda/react-native-boilerplate/blob/master/src/utils/connector.js)を使用します。本来Redux store, dispatch関数を利用する際、react-reduxより、connectを取得し、mapStateToProps, mapDispatchToPropsでバインドする必要があったのですが、connectorを使用することで簡潔に記述することができます。[サンプル](https://github.com/WataruMaeda/react-native-boilerplate/blob/master/src/routes/routes.js#L27-L40)を参照してください。connectorでcomponentをバインドしたのち、dispatch関数、stateをpropsより利用できます。

#### Reduxモジュールの分割

一つのファイルにstore, dispatch関数を追加し続けると、ファイルが肥大化し、ソースコードの管理が難しくなります。その場合、ファイルを分割する必要があります。全てのredux store, 関数は [modules](https://github.com/WataruMaeda/react-native-boilerplate/tree/master/src/modules)フォルダ配下で管理しています。デフォルトで`app.module.js`がありますが、別のモジュールtest.module.jsを新たに追加するとします。`/modules` フォルダ配下に、以下のtest.module.jsを追加します。

```/modules/test.module.js
// ------------------------------------
// Constants
// ------------------------------------

const KEY_CHECKED = 'KEY_CHECKED'

const initialState = {
  checked: false,
}

// ------------------------------------
// Actions
// ------------------------------------

export const isChecked = () => dispatch => dispatch({
  type: KEY_CHECKED,
  checked: true,
})

export const actions = {
  isChecked
}

// ------------------------------------
// Action Handlers
// ------------------------------------

const ACTION_HANDLERS = {
  [KEY_CHECKED]: (state, { checked }) => ({
    ...state,
    checked,
  }),
}

// ------------------------------------
// Reducer
// ------------------------------------

export default function reducer(state = initialState, action) {
  const handler = ACTION_HANDLERS[action.type]

  return handler ? handler(state, action) : state
}
```

次に、作成したモジュールを、[connector](https://github.com/WataruMaeda/react-native-boilerplate/blob/master/src/utils/connector.js)とつなげます。

```utils/connector.js
import { actions as testActions } from '../modules/test.module'  // *追加
...

const mapDispatchToProps = (dispatch) => {
  const actionList = [
    { label: 'app', value: appActions },
    { label: 'test', value: testActions },    // *追加
  ]
...
```

さらに、[store](https://github.com/WataruMaeda/react-native-boilerplate/blob/master/src/utils/store.js)にも追加します。

```utils/store.js
import { actions as testActions } from '../modules/test.module'  // *追加
...

// Redux store config

const configureStore = (initialState = {}) => {
  const reducers = combineReducers({
    app,
    test,  // *追加
  })
```

あとは、[サンプル](https://github.com/WataruMaeda/react-native-boilerplate/blob/master/src/routes/routes.js)のように、コンポーネントを[connector](https://github.com/WataruMaeda/react-native-boilerplate/blob/master/src/routes/routes.js#L27-L40)でラップし、propsより、[dispatch関数](https://github.com/WataruMaeda/react-native-boilerplate/blob/master/src/routes/routes.js#L10-L12)を呼び出したり、[redux store](https://github.com/WataruMaeda/react-native-boilerplate/blob/master/src/routes/routes.js#L16)にアクセスします。

## アセット

画像、フォント、色は、全て`/styles`配下で管理しています。それらを使用する際、毎回requireでインポートするのではなく、styleフォルダ配下で一限管理することにより、コードをより短く簡潔にし、新しい画像、フォント、色を追加した際にでも、どこに何を記述すればいいのかわかりやすくなります。

```expample.js
import { Image } from 'react-native'
import { colors, fonts, images } from '../../styles'
...

const styles = {
  title: {
    fontFamily: fonts.openSan.regular,
    color: colors.purple,
  }
}
...

<Image source={images.logo_sm} style={{ width: 32, height: 32 }} />
```

※ 画像、フォントはアプリ起動時、前もって読み込むことで、表示がワンテンポ遅れる問題を解消しています。


## 公開リンク

expoで公開しています。
https://expo.io/@wataru/react-native-boilerplate

プロジェクトはこちらにあります。(Github)
https://github.com/WataruMaeda/react-native-boilerplate



最後まで読んでいただきありがとうございました。

***Happy Cording!!***