ついんてっく

双子のエンジニア(兄:web、弟:インフラ)が気になること、今やってるプロジェクトとかについて書きます。ぶっちゃけ1人でやって続かなかったから監視体制を置いたのが強いサボりん坊や

React Native ~ Reduxの導入1 ~

Reduxの導入

どうも兄です。

前回に引き続き、reduxの導入をやっていきたいと思います。

twins-tech.hatenablog.com

今回、reduxの導入はreact-native-router-fluxを用いようと思います。

github.com

reduxの導入は、react-native-router-fluxを使わずともできますが、 flux/reduxに対応しており導入が楽なのと、 routingの際、stateが自動的にpropsとして渡ってきてとても便利です。

次の構成を目指して構築していきます。

続きを書きました。

twins-tech.hatenablog.com

 app
    ├── actions       // 関数。http通信や、データの処理をする
    ├── components   // containerからpropsを受け取る部品
    ├── containers      // reducerの受け口、pageのような部品
    ├── reducers        // action受け、新しいstateを返す
    └── route.js          // routingを行う。storeが一番上から渡る。

reduxの内容に関しては、ここではまとめず、良記事に委ねることにします。

qiita.com

Reduxの登場人物(Action、Reducers、Storeの詳しい説明。連携方法)をまとめてくれています。

必要なlibralyのインスコ

react-native-router-fluxが入った前提で、進めます。

1.redux 言わずとしれた今回の主役。インスコ npm i -S redux

2.react-redux reduxは独自で動くものなので、Componentと紐づける必要があります。react-reduxはProviderやconnectで繋いでくれます。 これもインスコ npm i -S react-redux

3.各種ミドルウェア Reduxはthin framworkを目指しているので、基本機能以外は外出しです。 今回は非同期処理と、ログ機能のミドルウェアを入れたいと思います。 npm i -S redux-thunk redux-logger

これで必要なものの準備は整いました。 それではReduxを導入していきましょう

Goal

みんな大好きTodo App的なものを目指していきたいと思います。

Actionの作成

Actionは、主にユーザーの行動で発生したイベントによって変更した値をstoreへと送る関数となります。 ActionでHTTP通信や、データの加工を行うのが正しいようです。 どのようにStoreに送るかというと後述にはなりますが、Containersで

dispatch(action())

ラップすることによってStoreに通知がいきます。

まずはファイルを作成しましょう。名前は何でもいいですが、できる限りReducerと対になっている方がわかりやすいです。

actions
    ├── todo.js

続いて、中身を作成していきます。 Actionsでは、必ず一つのタイプ(Reducerで識別するための定数)と、任意の値を返却します。

export const TEXT_UPDATE = 'TEXT_UPDATE'
export function fetchTextUpdate(text){
    return{
        type: TEXT_UPDATE,
        text: text,
    }
}

Actionには必ず、タイプの返却が必要です。 なぜなら、ActionはStoreに変更を通知しますが、ボールを投げるだけです。 どこに行くなんか知ったこっちゃないです。

それを華麗に受け取り、新しいstateを返すのがreducerの役割です。 僕はここの理解で一度つまづきました。。。

Reducer

ReducerはActionからStoreに投げられた通知を受け取り、Stateを更新するための関数です。

次の構成を目指します。

reducers
    ├── index.js
    ├── todo-reducer.js  /// 下にreducerが増えていく

index.jsは、Reducerをまとめ上げる役割としてあります。(子Reducer) 何故この機能が必要かというと、アプリが大きくなるごとに1つのReducerでは複雑かつわかりにくくなるからです。

MVCのModel設計のようなものと捉えています。増えない事はまずありえないので、初めからこの設計にしておくと良いです。

index.jsでは子Reducerをまとめて返してあげます。 こうすることで、1つのstoreに複数のReducerがぶら下がったStateを作ることができます。combineReducersといいます。

// index.js

import { combineReducers } from 'redux';
import todoReducers from './todo-reducer';

export default combineReducers({
  todoReducers, 
  aReducers,              // 作るたび追加していく
  bReducers,
})

Reducerを作成します。 Actionで返された値は、Storeを通じで全Reducerが受け取ります。ここで識別するためにtypeが必要となります。Reducerではtypeで条件分岐して新しいStateを返します。

// actionのtypeをimportしておく
import {
  TEXT_UPDATE,
} from '../actions/todo';

// 初期化
const initialState = {
  text: '',
};

// 入り口は一つにしてswitchで分ける。
// stateは前回のstateもしくはinitialStateが
// actionはactionでreturnしたvalueが渡る。
// reducer内ではstateを更新することはせず、新しいstate(assignして)を返す。
export default function todoReducer(state = initialState, action = {}){
  // typeでswitchする
  switch (action.type){
    case TEXT_UPDATE:
      // 空のObnectにマージする
      return Object.assign({}, state, {
        text: action.text,
      })
    // 初期状態を返してあげる必要がある。
    default:
      return state;    
  }
}

Store

アプリケーション全体の状態(State)を管理するオブジェクトです。 Routingと紐づけるために、react-naitve-router-fluxをimportしているroute.jsに記述します。

必要なlibralyをimportします。

import { Router, Scene, Modal, Switch } from 'react-native-router-flux';
import { createStore, applyMiddleware, compose } from 'redux';     // redux。storeの作成やmiddlewareの提供
import { Provider, connect } from 'react-redux';             // reduxとreact-nativeを関連づけてくれる
import thunkMiddleware from 'redux-thunk';                         // reduxの非同期処理(middlewareの例)
import createLogger  from 'redux-logger';                          // log出力(middlewareの例)

router-fluxとstoreを関連づけます。 connectはclassとreduxを関連づけてくれるmethodです。 Containerでも登場します。

import { Router, Scene, Modal, Switch } from 'react-native-router-flux';

import reducers from './reducers';
const RouterWithRedux = connect()(Router);

reducers、middlewareを持った、storeを作成します。 Storeはアプリケーションに1つでStoreがアプリケーションの状態(State)を保持します。

// create store...
const loggerMiddleware = createLogger();
const middleware = [thunkMiddleware, loggerMiddleware];
const store = compose(
  applyMiddleware(...middleware)
)(createStore)(reducers);

Storeが作成できたので、Routingと紐付けをしましょう。 をStoreとconnectされたRouterWithReduxに置き換えます。 でラップすることで、StoreからStateが自動的に下層コンポーネントに渡るようになります。

  render() {
    return (
      <Provider store={store}>
        <RouterWithRedux>
          <Scene key="root">
            <Scene key="drawer" component={Drawer}>
              <Scene key="tabbar" tabs={true} >
                <Scene key="BasicComponents" hideNavBar={false} hideTabBar={true} initial={true} component={BasicComponents} title="Basic" icon={TabIcon}/>
                <Scene key="ApiComponents" hideNavBar={false} component={ApiComponents} title="Api" icon={TabIcon}/>
                <Scene key="AwesomeLibralies" hideNavBar={false} component={AwesomeLibralies} title="Awesome" icon={TabIcon}>
                </Scene>
                
                <Scene key="Hardwares" hideNavBar={false} component={Hardwares} title="Hardwares" icon={TabIcon}/>
              </Scene>
            </Scene>
            <Scene key="ImagePage" component={ImagePage} title="ImagePage" icon={TabIcon}/>
            <Scene key="Blur" hideNavBar={false} component={Blur} title="Blur" sceneStyle={{marginTop: 64,}} />
            <Scene key="Video" hideNavBar={false} component={Video} title="Video" sceneStyle={{marginTop: 64,}} />
            <Scene key="ParallaxView" hideNavBar={false} component={ParallaxView} title="ParallaxView" sceneStyle={{marginTop: 64,}} />
          </Scene>
        </RouterWithRedux>
      </Provider>
    )
  } 

Reduxが導入できているか確認

Componentの作成はまだですが、実はReduxが導入できたか確認する事ができます。 何故なら初期状態はreducersのinitialStateで定義されているからです。

アプリを起動してcommand + DでDebug Js Remotelyを選択します。 f:id:twins_tech:20160702091038p:plain

これでDebugができるようになります。 Chrome Developerから確認することができます。

f:id:twins_tech:20160702091302p:plain

redux-loggerにより、logが出力されています。 初期状態を空にしてしまったのでわかりにくいですが、しっかりと、nextStateの下にtodoReducerがぶら下がっているのが確認できますね。

Viewとの接続までやりたかったのですが、長くなっってきましたので、次回にしたいと思います。

次回は、導入できたreduxをどうViewと紐づけるかをやってみたいと思います。