Expo+Redux(+firebase)でログインフォーム②　〜Reduxの導入〜

# はじめに
[Expo+Redux(+firebase)でログインフォーム①　〜概要・Expoの準備〜](https://qiita.com/AtsushiNi/items/5a112d4cd9a60566bf32)の続きです。

今回の記事では、Reduxを使って、まずは簡単なカウンターを作っていきます。
次々回の記事で、ログイン画面と画面遷移を実装していく予定です。

# 現在のディレクトリ構成

```
root/
 ├ App.js
 ├ package.json
```

# srcディレクトリの作成

rootに色々と詰め込んでいくとわかりにくくなるので、srcディレクトリを作ってAppを移しましょう
Expoはデフォルトでroot/App.jsを読み込むようになっているので、これを変更するために、node_modules/expo/AppEntry.jsを編集します

```Javascript:/node_modules/expo/AppEntry.js
-- import App from '../../App';
++ import App from '../../src/App';
```
そしてroot/App.jsを削除し、root/src/App.tsxを書いていきます。

# Reduxを導入
まずはApp.tsxにreduxを入れていきます
とりあえず最小の構成で、1ファイルにまとめています

```Javascript:src/App.tsx
import React from 'react';
import { StyleSheet, Text, View, Button } from 'react-native';
import { createStore, combineReducers } from 'redux'
import { Provider, connect } from 'react-redux'

// reducer
function counter(state, action) {
  if (action.type === 'undefined') {
    return null
  }

  switch(action.type) {
    case 'INCREMENT':
      return state + 1
    case 'DECREMENT':
      return state - 1
    default:
      return null
  }
}

// store
const store = createStore(combineReducers({ count: counter }))

// component
function Counter({ count, dispatch }) {
  return (
    <View style={styles.container}>
      <Text style={styles.paragraph}>{count}</Text>
      <Button
        title='Increment'
        onPress={() => dispatch({ type: 'INCREMENT' })}
      />
      <Button
        title='DECREMENT'
        onPress={() => dispatch({ type: 'DECREMENT' })}
      />
    </View>
  )
}

// container
const CounterContainer = connect(state => ({ count: state.count }))(Counter)

export default function App() {
  return (
    <Provider store={store}>
      <CounterContainer />
    </Provider>
  )
}

// スタイル
const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#ecf0f1',
    padding: 8,
  },
  paragraph: {
    margin: 24,
    fontSize: 18,
    fontWeight: 'bold',
    textAlign: 'center',
  },
})
```

### Reducer

```Javascript:App.tsx
// reducer
function counter(state, action) {
  if (action.type === 'undefined') {
    return null
  }

  switch(action.type) {
    case 'INCREMENT':
      return state + 1
    case 'DECREMENT':
      return state - 1
    default:
      return null
  }
}
```
actionと現在のstateを受け取って、新しいstateを返す関数です。
actionがない時や、swich文のdefaultでnullを返すようにしないとエラーになるので、注意が必要です。

### Store

```Javascript:root/App.tsx
// store
const store = createStore(combineReducers({ count: counter }))
```
Reducerが今後増えることを踏まえて、combineReducerでまとめておきます。

### Component/Container

```Javascript:App.tsx
// component
function Counter({ count, dispatch }) {
  return (
    <View style={styles.container}>
      <Text style={styles.paragraph}>{count}</Text>
      <Button
        title='Increment'
        onPress={() => dispatch({ type: 'INCREMENT' })}
      />
      <Button
        title='DECREMENT'
        onPress={() => dispatch({ type: 'DECREMENT' })}
      />
    </View>
  )
}

// container
const CounterContainer = connect(state => ({ count: state.count }))(Counter)
```
画面に表示する部分です。
ボタンを押すと、typeがINCREMENT/DECREMENTのactionがdispatchされます。
ボタンを押した時のコールバック関数は、本来はContainer側に書くべきだとは思いますが、コードが少しややこしくなるので、それは後にしようと思います。

### App
```Javascript:App.tsx
export default function App() {
  return (
    <Provider store={store}>
      <CounterContainer />
    </Provider>
  )
}
```
ReactNativeは、<Provider>で囲ってstoreを渡してやれば、Reduxと接続することができます。

ここまでで実行するとこんな感じ
<img width="300" alt="gif.gif" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/549051/54175055-c3db-6609-aa8e-2d28e27fa7ff.gif">

# 次回
今回は最低構成のreduxを組んでみたので、次回はDucksパターンを意識してファイルを分けていきたいと思います。
その後、ReactNativeDebuggerも使えるようにしていきます。
[Expo+Redux(+firebase)でログインフォーム③　〜ファイル整理・Debugger〜](https://qiita.com/AtsushiNi/items/13fc1f94674d9614e43f)