react-reduxのmapStateToPropsとmapDispatchToPropsは必要なのか

アプリエンジニアの原です。

ReactNativeでアプリを作る時はReduxを使っていて、ReduxのStoreとReactのComponentをBindingしてくれるreact-reduxが便利なので使っています。

github.com

react-reduxが提供するconnectというStoreとComponentをBindingする関数が在るのですが、

connect(
  mapStateToProps,
  mapDispatchToProps,
  mergeProps,
)(Component)

という感じで、3つの関数を引き数にしています。

mapStateToProps

これはReduxのStoreを第一引き数に取る関数で、ComponentにPropsとして渡すものをフィルタリングしたい時に使います。例えばStoreのUserからnameをPropsとして渡したい場合は

//第一引き数はstore
const mapStateToProps = store => ({ name: store.user.name });

connect(mapStateToProps)(Component);

//Componentはthis.props.nameでアクセス可能

こんな感じでフィルタリングしてComponentにPropsと渡すことが出来ます。

mapDispatchToProps

これはReduxのDispatchを第一引き数に取る関数で、変更を伝えるアクションを作成する時に使います。例えばボタンが押された時にUserのnameを変更するアクションを作成する場合は

// 第一引き数はReduxのdispatch関数
const mapDispatchToProps = dispatch => ({ updateName: name => dispatch({ type: UPDATE_NAME, name }) });

connect(null, mapDispatchToProps)(Component);

//Componentはthis.props.updateName('name')でアクセス可能

こんな感じで作成したアクションをComponentのPropsとして渡すことが出来ます。

mergeProps

これは第一引き数にmapStateToProps、第二引き数にmapDispatchToPropsをとる関数で新しPropsを作成します。 デフォルトでは

Object.assign({}, ownProps, stateProps, dispatchProps)

という処理を行います。

(ownPropsはComponentが持つプロパティです。mapStateToProps, mapDispatchToProps共に第二引き数, mergePropsは第三引き数で受取ることができます)

疑問

結論からいうとmapStateToPropsとmapDispatchToPropsの使い所がわかりません

例えば, Userの情報をサーバーから取得してStoreを更新する処理を考えます。

ユーザーの情報を取得するAPIにはユーザーのトークンが必要なのでStoreから取得する必要があります。

サーバーから取得したレスポンスでStoreを更新するためにDispatch関数が必要です。

なので、mergePropsを使わない場合は

const mapStateToProps = store => ({ token: store.user.token });

const mapDispatchToProps = dispatch => ({ fetchUser: token => fetch(...) }  });

connect(
  mapStateToProps,
  mapDispatchToProps,
)(Component);

のようになり、ComponentがfetchUserにトークンを渡す責務が発生します。

Componentは表示やUIイベントの通知に関わる処理だけをするべきであり、またtoken自体描画には全く関係無いのでPropsとして渡すのは不適切な気がします。

なので、ここでmergePropsを使ってtokenを渡す処理を内包すると

const mapStateToProps = store => ({ 
  token: store.user.token 
});

const mapDispatchToProps = dispatch => ({ 
  fetchUser: token  => fetch(...),  //tokenを受け取ってfetchを実行するための関数を返す。
});

const mergeProps = (state, action) => ({
     fetchUser: () => action.fetchUser(state.token),
});

connect(
  mapStateToProps,
  mapDispatchToProps,
  mergeProps
)(Component);

これでも可能ですが、

なんなら

const mapStateToProps = store => store;

const mapDispatchToProps = dispatch => ({ dispatch });

const mergeProps = (store, { dispatch })  => ({
    fetchUser: () => {
        const token = store.user.token;
        fetch(...);
    }
});

connect(
  mapStateToProps,
  mapDispatchToProps,
  mergeProps
)(Component);

コレが一番シンプルなのでは??

という結論に今のところ至っており、

mapStateToPropsとmapDispatchToPropsは必要なのか

です。

だれか教えてください。よろしくお願いします!

宣伝

このイベントでReactNativeに関する発表をします。

connpass.com

ReactNativeのView構造をAndroidDeviceMonitorで確認する

こんにちは。アプリエンジニアの原です。

テックブログ始めました。

一発目はReactNativeの記事です。

巷で話題のReactNative、最近弊社のネイティブアプリをReactNative化するプロジェクトがスタートしています。

ReactNativeはロジック部分をJavascriptで、Viewの部分をJavascriptのライブラリのReactで書くことができ、実行時はReactで書いたソースからiOS/AndroidのネイティブコードのViewに変換してくれます。

では実際にReactNativeのコードからどのようなViewに変換されてるでしょうか。

ソースを読むのも1つの手ですが、AndroidSDKにはAndroidDeviceMonitorというツールがありViewツリーをGUIで確認できる機能があります。このツールを使うことでViewの構造を簡単に確認することが出来ますので今回をこれを使って見てみます。

新規プロジェクトを作成

ReactNativeとAndroidSDKの設定はできてい前提で進めていきます。

ReactNativeの環境設定はこちら facebook.github.io

AndroidStudioまたはコマンドラインツールズのダウンロードはこちら developer.android.com

react-native init で新規作成、react-native run-androidでアンドロイドのビルドが走ります。

react-native init AnectSample1
cd AnectSample1
react-native run-android

新規のプロジェクトだと下記のような画面になります。 f:id:anect:20170227233703p:plain

Javascript側のView構造は下記のようになっています。

/**
 * Sample React Native App
 * https://github.com/facebook/react-native
 * @flow
 */

import React, { Component } from 'react';
import {
  AppRegistry,
  StyleSheet,
  Text,
  View
} from 'react-native';

export default class AnectSample1 extends Component {
  render() {
    return (
      <View style={styles.container}>
        <Text style={styles.welcome}>
          Welcome to React Native!
        </Text>
        <Text style={styles.instructions}>
          To get started, edit index.android.js
        </Text>
        <Text style={styles.instructions}>
          Double tap R on your keyboard to reload,{'\n'}
          Shake or press menu button for dev menu
        </Text>
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#F5FCFF',
  },
  welcome: {
    fontSize: 20,
    textAlign: 'center',
    margin: 10,
  },
  instructions: {
    textAlign: 'center',
    color: '#333333',
    marginBottom: 5,
  },
});

AppRegistry.registerComponent('AnectSample1', () => AnectSample1);

render()メソッドがViewを返します。

Viewの中にTextが3つ入っている構造です。

AndroidDeviceMonitorで確認

このViewをAndroidDeviceMonitorで確認してみます。

monitor

パスが通っていない場合はAndroid/SDK/toolsの中にあります。

AndroidDeviceMonitorが起動したら下図の赤丸の部分をおしてHierarchyViewを選択します。

f:id:anect:20170228005304p:plain

左側のナビゲーションのwindowsから作成したアプリを選択すると下記の図のようにViewの構造が表示されます。

f:id:anect:20170228005619p:plain

変換されたViewを探す

一番左がルートのViewになり階層順に右に並んでいます。 右端の一番上のReactTextViewをクリックしてみると

f:id:anect:20170228011347p:plain

となり、このViewが「Welcome to React Native」を表示していることがわかります。

同様に残り2つのReactTextViewもJavascript側で書いたテキストを表示していることがわかりました。

このことからJavascript側のTextクラスがAndroid側ではReactTextViewというViewとに変換されているということが確認できました。

またこれらを子Viewに持つReactViewGroupがJavascript側のViewクラスが変換されたものだということもわかり、Javascriptで定義したすべてのViewがネイティブのコードに変換されていることが確認できました。

まとめ

RectNativeで作成したアプリをAndroidDeviceMonitorでViewの構造を調べました。 Javascript側のViewがAndroid側のネイティブのViewとしてちゃんと変換されていることがわかりました。 実際の実装で頻繁に使うことは無いと思いますが、ReactNativeのコンポーネントがネイティブでどう変換されているかを知ることは実装やデバッグに役に立つこともあるかと思いますので機会があれば使ってみてください。