React Native

初心者がReduxToolkitを使ってみた。

React Native
この記事は約18分で読めます。

初めに

本記事は未経験で入社し1ヶ月になる私がまとめた記事になります。
まだまだ学習中ですが、ご指摘等ございましたらご教授頂けると幸いです。
私自身、日本語が上手ではないのでお見苦しい部分もあるかと思いますがご了承ください。

概要

まずは実装する環境についてですが、ReactNativeで書いてますのでReactでの書き方と違いが出てきますことご了承ください。
本記事の対象の方は「最近React,ReactNativeを学習し始めました!」くらいのレベル感でしょうか。

今回ReduxToolkitを理解する上でまず最初にReduxの理解を深めて行きたいと思います。
そこから、Reduxをより簡潔に記述するためのツールであるReduxToolkitについて見ていきましょう。

まずはReact Nativeの公式ドキュメントと、
Reactの公式ドキュメントを貼らせていただきますので適宜参照いただくのがいいと思います。
私は英語が苦手ですので脳死で翻訳を使っていつも見てます。

ReactNativeにおけるStateの管理

まずReduxを使わずに、Stateを管理する方法をみていきましょう。
とりあえずプロジェクトを作成します。

react-native init ReduxSample

まずはReact、ReactNativeに置いてコンポーネント間でStateを参照する際に、

/src/component/Component1.js

import React from 'react';
import {SafeAreaView,View} from 'react-native';
import Component2 from './Component2'

const Component1 = () => {
  const [name, setName] = useState("hoge")
    return(
      <SafeAreaView>
        <View>
          <Component2 name={name}/>
        </View>
      </SafeAreaView>
    )
}

src/component/Component2.js

import React from 'react';
import Component3 from './Component3'

const Component2 = props => {
  return(
    <SecondComponent props={props}/>
  )
}

src/component/Component3.js

import React from 'react';
import {Text} from 'react-native';

const Component3 = props => {
  return(
      <Text>{props.props.name}</Text>
  )
}


こんな感じでバケツリレーで渡したりしますよね。
上記くらいの処理(普通こんな書き方はしないですが)なら目ですぐに終える程度ですが、
もっとコンポーネントが増えて複雑になると中々難しくなってくるところです。

例えば下図のようなコンポーネントの構成があった場合に、

「Component⑧」や「Component⑮」で「Component①」のstateを参照したい!!となった場合には長いバケツリレーになってしまいます。
下図ような感じですね。

この煩わしさを無くす為に導入を勧めているのがReduxというわけです。

Reduxについて

まずは公式ドキュメントをペタッと。

ドキュメントの中で一文引用すると(※Google翻訳様のお力を借りてます。)、

Reduxは、「アクション」と呼ばれるイベントを使用して、アプリケーションの状態を管理および更新するためのパターンおよびライブラリです。これは、アプリケーション全体で使用する必要がある状態の集中ストアとして機能し、状態を予測可能な方法でのみ更新できるようにするルールがあります。

ほーん。。。。
状態と翻訳されているのがstateのことですね。
この一文で全てを理解するのは無理なので、もう少し深堀していきましょう。

Reduxを使う場合にstateを管理する場合は、
先ほど紹介した図を用いて説明すると、

ほい。
storeでstateを管理できるのでバケツリレーの必要がありません。
どこのコンポーネントからでもstateを参照できるのです。
便利ですね。

そしてReduxの実装には
・store(変更された状態を管理する)
・reducer(actionにから受けた変更を管理する)
・action(ユーザー操作等で変更があった際にそれを依頼)
が必要になって行きます。

これに関しては言葉で説明するのは難しい部分でもあるので、実際にReduxを使ってみて覚えて行きましょう。

まずはプロジェクトにreduxをインストール。

npm install redux

では先ほど作ったプロジェクト内に記載して行きます。
今回は簡易的なサインインの画面を作成し、入力されたものを違うコンポーネントでreduxを使って参照するといったものを作ってみます。

まずactionの準備から。

src/redux/actions.js

export const SIGN_IN = "SIGN_IN";
export const signInAction = (state) => {
    return {
        //actionのタイプ(reducerで使います)
        type: "SIGN_IN",
        payload: {
            //サインイン状態にして、EmailとPassをstateに保持
            isSignedIn: true,
            userEmail: state.userEmail,
            userPassword: state.userPassword
        }
    }
};

export const SIGN_OUT = "SIGN_OUT";
export const signOutAction = () => {
    return {
        type: "SIGN_OUT",
        payload: {
            //サインイン状態をfalseに,EmailとPassを空に。
            isSignedIn: false,
            userEmail: "",
            userPassword: ""
        }
    }
};

次にreducer準備。

src/redux/reducers.js

import * as Action from './actions'

//初期のstate
const initialState = {
    users: {
        isSignedIn: false,
        userEmail: "",
        userPassword: ""
    }
};

export const usersReducer = (state = initialState.users, action) => {
    switch (action.type) {
        case Action.SIGN_IN:
            return {
                //現在のstateを保持
                ...state,
                //signInActionで定義したstateをセット
                ...action.payload
            }
        case Action.SIGN_OUT:
            return {
                //initialStateに戻す
                state
            }
        default:
            return state
    }
}

次にstoreの準備

src/redux/store.js

import { createStore as reduxCreateStore, combineReducers } from 'redux';
import { usersReducer } from './reducers'

export default function createStore() {
    return reduxCreateStore(
        combineReducers({
            users: usersReducer,
        })
    );
};
export const store = createStore();

ほい。これでreduxの素材が揃いました。
今回の超簡易的なサインイン画面とHomeを作って行きます。
※react navigationを今回は使いますが、ここでは紹介いたしませんのでお分かりにならない方はご自身で調べていただきますようお願いいたします。

まずApp.jsApp.js

import React from 'react';
import {Provider} from 'react-redux';
import Router from './src/components/Router';
import {store} from './src/redux/store'

const App = () => {
  return (
    //reduxを使用するComponentはProbiderで囲う必要があります
    <Provider store={store}>
      <Router />
    </Provider>
  );
};

export default App;

次に画面遷移を処理するRouterを作ります。

src/component/Router.js

import React from 'react';
import {createDrawerNavigator} from '@react-navigation/drawer';
import {NavigationContainer} from '@react-navigation/native';
import SignInScreen from '../screens/SignInScreen';
import SignOutScreen from '../screens/SignOutScreen';
import {useSelector} from 'react-redux';
import Home from '../screens/Home';

//react navigation
const Drawer = createDrawerNavigator();

const Router = () => {
  //reduxでstateを参照する時はuseSelectorを使います
  const selector = useSelector((state) => state);

  return (
    <>
      {/*isSignedInがtrueならHomeへ。falseならsignInへ。起動して初期はfalse */}
      {selector.users.isSignedIn ? (
        <NavigationContainer>
          <Drawer.Navigator initialRouteName="Home">
            <Drawer.Screen name="Home" component={Home} />
            <Drawer.Screen name="SignOutScreen" component={SignOutScreen} />
          </Drawer.Navigator>
        </NavigationContainer>
      ) : (
        <NavigationContainer>
          <Drawer.Navigator initialRouteName="SignInScreen">
            <Drawer.Screen name="SignInScreen" component={SignInScreen} />
          </Drawer.Navigator>
        </NavigationContainer>
      )}
    </>
  );
};

export default Router;

次にサインイン画面を作ります

src/component/signInScreen.js

import React, {useState} from 'react';
import {Button,View,Text,SafeAreaView,StyleSheet,TextInput,} from 'react-native';
import {useDispatch} from 'react-redux';
import {signInAction} from '../redux/redux';

const SignInScreen = () => {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');

  //reduxでstateを更新する際に下記の定義が必要
  const dispatch = useDispatch();

  const signIn = (email, password) => {
    //定義したactionをdispatchすることでstateを更新します。
    dispatch(signInAction({userEmail: email, userPassword: password}));
  /*これでサインイン状態になり、stateにEmailとPassが入ります。その状態でreturnすることで
    isSignedInはtrueのためHomeへ遷移するという流れ。 */
    return;
  };

  //以下はスタイルと画面描写
  return (
    <SafeAreaView>
      <View style={styles.TopView}>
        <Text style={{fontSize: 20}}>サインイン</Text>
      </View>
      <View>
        <View style={styles.input}>
          <TextInput
            placeholder={'メールアドレス'}
            value={email}
            autoCapitalize="none"
            onChangeText={setEmail}
          />
        </View>
        <View style={styles.input}>
          <TextInput
            placeholder={'パスワード'}
            type={'password'}
            secureTextEntry={true}
            onChangeText={setPassword}
          />
        </View>
        <View style={styles.submitButton}>
          <Button title="サインイン" onPress={() => signIn(email, password)} />
        </View>
      </View>
    </SafeAreaView>
  );
};

export default SignInScreen;

const styles = StyleSheet.create({
  TopView: {
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'center',
    marginVertical: 10,
  },
  submitButton: {
    marginVertical: 10,
  },
  input: {
    padding: 10,
    marginTop: 30,
    borderRadius: 10,
    backgroundColor: '#ffffff',
    shadowOffset: {
      width: 0,
      height: 4,
    },
    shadowOpacity: 0.21,
    shadowRadius: 4,
    elevation: 7,
    marginHorizontal: 20,
  },
});

次にHomeを作成

src/component/Home.js

import React from 'react';
import {SafeAreaView, Text, View, StyleSheet} from 'react-native';
import {useSelector} from 'react-redux';

const Home = () => {
  //先ほど入力したものがreduxStateに入る
  const reduxState = useSelector((state) => state);
  //isSignedInはtrueと出力されます
  console.log(reduxState.users.isSignedIn);
  return (
    <SafeAreaView>
      <View style={styles.container}>
        <Text style={styles.text}>
          入力されたEmailは「{reduxState.users.userEmail}」です。
        </Text>
        <Text style={styles.text}>
          入力されたPasswordは「{reduxState.users.userPassword}」です。
        </Text>
      </View>
    </SafeAreaView>
  );
};

export default Home;

const styles = StyleSheet.create({
  container: {
    margin: 30,
  },
  text: {
    fontSize: 20,
  },
});

はい。以上です。

ReduxToolkitについて

ReduxToolkitの公式ドキュメントを拝見してみましょう。

Reduxのツールキットパッケージは、書くための標準的な方法であることを意図しているReduxののロジックを。もともとは、Reduxに関する3つの一般的な懸念に対処するために作成されました。
・「Reduxストアの設定は複雑すぎます」
・「Reduxに何か便利なことをさせるには、たくさんのパッケージを追加する必要があります」
・「Reduxにはボイラープレートコードが多すぎます」
私たちは、すべてのユースケースを解決しますが、の精神ですることはできませんcreate-react-appし、apollo-boost我々は同様に、ユーザの簡素化できるようになるいくつかの便利なユーティリティが含まれてセットアッププロセスを抽象的には、最も一般的なユースケースを扱うことを、いくつかのツールを提供しようとすることができ、それらのアプリケーションコード。
これらのツールは、すべてのReduxユーザーにとって有益なはずです。あなたが最初のプロジェクトを設定する新しいReduxのユーザー、または既存のアプリケーションを簡単にするために望んでいる経験豊富なユーザーしているかどうか、Reduxのツールキットは、あなたがより良いあなたのReduxのコードを作ることができます。

うむ。翻訳された文章ではわかりません。
英語から逃げてはいけないという暗示でしょうか。

実際にコードを書いて動かしてみるのがいいでしょう。

ではまずプロジェクトにReduxToolkitをインストールします。

npm install @reduxjs/toolkit

先ほどのReduxのコードをReduxToolkitに置き換えて行きます。
今回は記述量もそこまで多くないので1ファイルにまとめて書いちゃいます。

src/redux/redux.js

import { createSlice } from '@reduxjs/toolkit';
import { configureStore } from '@reduxjs/toolkit'

const initialState = {
  isSignedIn: false,
  userEmail: "",
  userPassword: ""
};

//reducerとactionをまとめて定義します
const signInSlice = createSlice({
  //slice名
  name: 'usersReducer',   
  initialState, 
  reducers: {
    signInAction: (state, action) => {  
      return{
        ...state,
        userEmail: action.payload.userEmail,
        userPassword: action.payload.userPassword,
        isSignedIn: true
      }
    }
  }
})

export const {signInAction} = signInSlice.actions

//store
export const store = configureStore({
  reducer: signInSlice.reducer
})

以上!!
これであとはインポート先を変更すれば同じ動きになります。
うむ。
記述量は確かに減りましたね。便利です。
これでReduxToolkitを使って実装できました。

感想

いかがでしたでしょうか。
私自身で記事を書いた後に見返しましたが、中々に初心者感丸出しです。
うまく言葉で説明できずに学習不足を痛感していますが、このようにアウトプットすることでまた一つ学習になると感じました。

今回はReduxの紹介ということでサインイン機能よりも簡潔にReduxのコードを伝えることを意識しました。
が、あまりにもガバガバな作りで心残りなので、入力チェックやfirebaseを使ってアカウントの管理をするようなログイン機能をまた別の記事で紹介したいと思います。
以上です。