React Nativeの環境構築や基本的なコンポーネントの使い方がわからない人は、上記記事を先に参照してください。
まずは、React Nativeのプロジェクトを作成して、
1 |
react-native init プロジェクト名 |
プロジェクトディレクトリの中で、reduxを使うために必要なモジュールをインポートして下さい。
1 |
npm i -S react-redux redux redux-thunk |
「react-redux」はReactとReduxをバインドするために必要なモジュールですので、「redux」と「react-redux」の両方をインストールする必要があります。
「redux-thunk」はReduxで非同期通信するために必要なライブラリですので、一緒にいれておきます。
また、認証機能にGoogleが買収したBaasサービスFirebaseを使いますので、
1 |
npm i -S firebase |
でfirebaseモジュールもインポートしておいてください。(Firebaseの基本的な使い方がわからない人は、コチラの記事を参照)
React Reduxとは
「そもそもReact Reduxとはなんなのか?」という疑問があるかと思いますが、ようは、ReactやReact Nativeで使用する「store」を管理しやすくする便利ツールです。
React Reduxを使わなくても、全く同じプロダクトをReact・React Nativeだけで作れます。しかし、Component間でデータを延々とバケツリレーのように受け渡しして、カオスな状態のプロダクトが出来てしまうでしょう。
React Reduxはそういったカオスなデータ管理にならないように、中央集権型のデータStoreの役割を担ってくれるライブラリです。
React Reduxで認証フォームを作る
それでは、早速作っていきましょう。今回の目的は「React Redux」を覚えるということなので、最小のファイル構成でいきます。
1 2 3 4 |
├── App.js ├── LoginForm.js ├── actions.js └── reducers.js |
この4つのファイル構成でreact reduxでログインフォームが作れます。
- App.js:大元の親コンポーネント
- LoginForm.js: ログインフォームを構築する子コンポーネント(View)
- actions.js: Viewで操作した動作を受け取って、reducersに指示を出す(Actions)
- reducers.js: Actionから受けた指示通りに全体で保有しているデータStoreを更新する(Reducer)
App.js(middlewareやreduxをreactに結合する)
まずは大元になるApp.jsをみていきましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
import React, { Component } from 'react'; import { View, Text } from 'react-native'; import { Provider } from 'react-redux'; import { createStore, applyMiddleware } from 'redux'; import firebase from 'firebase'; import ReduxThunk from 'redux-thunk'; import reducers from './reducers'; import LoginForm from './LoginForm'; class App extends Component { componentWillMount() { firebase.initializeApp({ apiKey: "dummydummydummy", authDomain: "dummy-dummy.firebaseapp.com", databaseURL: "https://dummy-dummy.firebaseio.com", projectId: "dummy-dummy", storageBucket: "dummy-dummy.appspot.com", messagingSenderId: "339440415242" }); } render() { return ( <Provider store={createStore(reducers, {}, applyMiddleware(ReduxThunk))}> <View style={{flex: 1}}> <View style={styles.header}><Text style={styles.headerText}>ログインフォーム</Text></View> <LoginForm /> </View> </Provider> ); } } const styles = { header: { backgroundColor: '#F8F8F8', justifyContent: 'center', alignItems: 'center', height: 60, paddingTop: 15, elevation: 2, position: 'relative' }, headerText: { fontSize: 20, fontWeight: '600' } }; export default App; |
基本的にApp.jsはReact Reduxでデータを管理するために必要なもの(reducerやmiddleware等)を結合する場所です。
1 2 3 4 5 |
<Provider store={createStore(reducers, {}, applyMiddleware(ReduxThunk))}> <View style={{flex: 1}}> .......... </View> </Provider> |
こういったrender()内の記述は、react reduxを使うときはお決まりで書くものとして、理解するというよりは覚えてしまいましょう。
今回App.jsで行っていることは
- 1〜6行目:React Reduxを非同期通信(ログイン処理など)で使用するために必要なモジュールをインポート
- 7〜8行目:reducersと子Componentをインポート
- 11〜19行目:firebaseを使えるようにする(firebaseでのログイン認証についてわからない人はコチラの記事を参照)
- 22〜28行目:お決まりの記述として覚えましょう。(ヘッダーとLoginFormを記述している部分以外はReact Reduxを使うおまじない的記述です)
LoginForm.js(View画面)
次は、ログインフォームを描写しているView画面にあたるLoginForm.jsを解説します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 |
import React, { Component } from 'react'; import { connect } from 'react-redux'; import { View, TouchableOpacity, Text, TextInput, ActivityIndicator } from 'react-native'; import firebase from 'firebase'; import { changeEmail, changePassword, submitLogin } from './actions'; class LoginForm extends Component { onButtonPress() { const { email, password } = this.props; this.props.submitLogin({ email, password }); } loadSpinner() { if (this.props.loading) { return <ActivityIndicator size="small" /> } return ( <TouchableOpacity onPress={this.onButtonPress.bind(this)} style={styles.buttonStyle}> <Text style={styles.textStyle}> ログイン </Text> </TouchableOpacity> ) } render() { return ( <View> <View style={styles.wrap}> <TextInput placeholder="user@gmail.com" autoCorrect={false} value={this.props.email} onChangeText={email => this.props.changeEmail(email)} style={styles.inputStyle} /> </View> <View style={styles.wrap}> <TextInput secureTextEntry placeholder="password" autoCorrect={false} value={this.props.password} onChangeText={password => this.props.changePassword(password)} style={styles.inputStyle} /> </View> <View style={styles.wrap}> {this.loadSpinner()} </View> <Text> {this.props.loggedIn} </Text> </View> ) } } const styles = { wrap: { padding: 10 }, textStyle: { alignSelf: 'center', color: '#007aff', fontSize: 16, fontWeight: '600', paddingBottom: 10, paddingTop: 10 }, buttonStyle: { alignSelf: 'stretch', backgroundColor: '#fff', borderRadius: 5, borderWidth: 1, borderColor: '#007aff' }, inputStyle: { color: '#000', paddingRight: 5, paddingLeft: 5, fontSize: 18, lineHeight: 23, height: 30, borderWidth: 1, borderColor: '#333' } } const mapStateToProps = state => { return { email: state.auth.email, password: state.auth.password, loading: state.auth.loading, loggedIn: state.auth.loggedIn } } export default connect(mapStateToProps ,{ changeEmail, changePassword, submitLogin })(LoginForm); |
少し長めですが、行っている処理は非常にシンプルなので、1つずつ説明していきます。
- 1〜4行目:必要なモジュール達をインポート
- 5行目:actions.js(Action)から、使いたいメソッドをインポート
- 8〜10行目:ログインボタンを押したときにActionにemailとpasswordを渡して、Action側で定義されたsubmitLogin()を実行する(ログイン認証機能)
- 13〜24行目:firebaseと通信中はStoreのauth.loadingがtrueになって、ローディングアイコンが出現する
- 27〜58行目:render()処理(emailやpasswordの値が変更される度にActionを呼び出して、storeのemailやpasswordの値が更新されるようになっている【onChangeText={email => this.props.changeEmail(email)}】)
- 93〜99行目:storeの呼び出し処理。ここで後ほど紹介するReducerで保持しているStoreの値を取得する。取得した値は、this.props.○○という呼び出し方で、React Component内で使用できる
- 102行目:connectでActionとReducerをこのコンポーネントに結合している。(こういうものだと思って覚えてしまいましょう)
actions.js(Action)
次は、Viewからの命令を受けとり、Reducerに値を受け渡すActionをみていきましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
import firebase from 'firebase'; export const changeEmail = (text) => { return { type: 'change_email', payload: text }; }; export const changePassword = (text) => { return { type: 'change_password', payload: text }; }; export const submitLogin = ({ email, password }) => { return (dispatch) => { dispatch({ type: 'login_start' }); firebase.auth().signInWithEmailAndPassword(email, password) .then(user => { dispatch({ type: 'login_success' }); dispatch({ type: 'login_end' }); }) .catch(() => { dispatch({ type: 'login_fail' }); dispatch({ type: 'login_end' }); }); } } |
基本的には、typeでReducerのどの処理を行うか指示して、payloadに渡したい値を代入します。
17〜31行目の処理はちょっとだけ特殊で、ログイン処理を行うためにはfirebaseに正しい認証情報かどうかを問い合わせる必要があります。そのfirebaseに問い合わせているタイムラグをうまく処理しています。(非同期処理)
Actionの役割は、Viewから指示が来たら、必要な処理を行って、処理後のデータをReducerに渡すことです。
reducers.js(Reducer)
最後に、値を受け取って、中央集権的にデータStoreを管理しているReducerを解説します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
import { combineReducers } from 'redux'; const INITIAL_STATE = { email: "", password: "", loading: false, loggedIn: "" } const AuthReducer = (state = INITIAL_STATE, action) => { switch (action.type) { case 'change_email': return { ...state, email: action.payload } case 'change_password': return { ...state, password: action.payload } case 'login_start': return { ...state, loading: true } case 'login_end': return { ...state, loading: false } case 'login_success': return { ...state, loggedIn: "ログイン中" } case 'login_fail': return { ...state, loggedIn: ""} default: return state; } }; export default combineReducers({ auth: AuthReducer }); |
- 3〜8行目:初期値
- 10〜27行目:どういった指示(type)がきたら、どの値を更新するかをここに記述します。switch文で現状のstateを上書きしていくこの書き方が一般的ですので、基本的にはこの型にそってReducerを書いていくことをオススメします。
- 29〜31行目: データstoreに当たる部分。ここにあるデータを更新し、View側から取得してアプリを作っていきます。
Reducerの役割はシンプルで、Actionから命令と値が飛んできたら、それに従ってデータStoreを更新するだけです。
一方通行のシンプルなデータの流れ
以上でReact Reduxで作るログインフォームは完成です。うまくいっていたら、

こういった画面が表示されると思います。
そして、接続しているFirebaseに登録されているUserでログインすると

【ログイン中】という文字が出現するようにしてあります笑
ちょっと、Reduxに慣れていない人にとっては複雑に感じるかもしれませんが、
- View側のデータを更新するために、Actionに命令を出す
- Viewの指示を受け、Actionでデータを生成して、Reducerに渡す
- ReducerはActionから来た指示通りにデータStoreの値を更新する
- ViewはReducerで更新された値を受け取り、再描写する
という流れで、データが一方通行にグルグル回っているだけです。
今回作成したアプリは、非常にシンプルにReact Reduxを利用して作ったネイティブアプリですので、このアプリでReact Reduxのデータ構造を覚えてしまってください。
現在のweb界隈のReact Nativeによるアプリ開発では、React Reduxは必須スキルになっていますので、頑張って覚えてくださいね!
今回作成したアプリを以下にアップしておきましたので、参考にしたい方は是非^^
https://github.com/rara-tan/react-native-auth-redux-flow