React Native

Redux toolkitでReduxをなるべく簡単に実装する

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

はじめに

Reduxって難しいですよね…。
でもアプリの規模によっては避けて通れない時もある。。
この記事はredux toolkitを使ってreduxをなるべく簡単に実装するための記事です。
React Nativeで実装していきます。

実現すること

  • storeからstateを取ってきて文字列を画面に表示するだけの実装(payloadとかはいったんなし)
  • ある程度規模が大きくなっても管理しやすい形にする

画面実装

ここら辺で実装されている画面をコピペするだけでOKです
HomeScreenとDetailScreenの2画面をnavigationで行き来するだけの画面です

React NativeでNavigationを使っていい感じに画面遷移する

ライブラリをインストールする

公式ドキュメントに記載されているライブラリに加えてreact-reduxもインストールしましょう

yarn add @reduxjs/toolkit
yarn add react-redux
yarn add redux

ちなみにどんなライブラリを入れたかというと

ライブラリ用途
@reduxjs/toolkitredux toolkit
react-reduxReactにReduxを組み込む方法を提供する。
reduxredux本体

一応pod installもやっておきましょう

cd ios && pod install && cd ..
react-native run-ios

これで下準備が完了!!

ディレクトリを用意する

src配下にreduxというディレクトリを用意し、その中にtodouserというディレクトリを用意しましょう


ファイルも用意する

todoの中にはtodoSlice.js
userの中にはuserSlice.jsを作成します。
これらがあとあと個別のstoreになります。(いったんまだ何も書かなくてOK)


createSliceを使って個別のstoreを作成

Redux Toolkit から提供される createSlice 関数を用いて個別のストアを作成します。
先ほど作成したtodoSlice.jsに処理を加えます。画面実装は以下をイメージ。

  • userTodoというstateの初期値はnull
  • setTodoを押した時に文字列にjoggingと入れる
  • resetTodoを押した時に初期値(null)に戻る

todoSlice.js

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

// Stateの初期状態
const todoInitialState = {
  userTodo: null,
};

// Sliceを生成し、exportする
export const todoSlice = createSlice({
  name: 'todo',
  initialState: todoInitialState,
  reducers: {
    setTodo: state => {
      state.userTodo = 'jogging';
    },
    resetTodo: state => {
      state.userTodo = todoInitialState.userTodo;
    },
  },
});

// Action Creatorsをエクスポート
export const {setTodo, resetTodo} = todoSlice.actions;

次にuserSlice.jsにも処理を加えます。
画面実装は以下をイメージしてます。

  • userNameとuserEmailというstateの初期値はnull
  • getInfoを押した時に文字列を入れる
  • resetInfoを押した時に初期値(null)に戻る

userSlice.js

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

const userInitialState = {
  userName: null,
  userEmail: null,
};

export const userSlice = createSlice({
  name: 'user',
  initialState: userInitialState,
  reducers: {
    getInfo: state => {
      state.userName = 'Tanaka Taro';
      state.userEmail = 'example@gmail.com';
    },
    resetInfo: state => {
      state.userName = userInitialState.userName;
      state.userEmail = userInitialState.userEmail;
    },
  },
});

export const {getInfo, resetInfo} = userSlice.actions;

reducerを結合する

reduxディレクトリ配下にreducers.jsを作成し、
Reduxが提供する combineReducers 関数で各 Slice の Reducer を結合します。

reducers.js

import {combineReducers} from '@reduxjs/toolkit';
import {userSlice} from './user/userSlice';
import {todoSlice} from './todo/todoSlice';

export const rootReducer = combineReducers({
  user: userSlice.reducer,
  todo: todoSlice.reducer,
});

storeを生成する

reduxディレクトリ配下にstore.jsを作成し、先ほど結合したReducerを configureStore 関数に渡してstoreを生成します

store.js

import {configureStore} from '@reduxjs/toolkit';
import {rootReducer} from './reducers';

const store = configureStore({reducer: rootReducer});

export default store;

完成したstoreをアプリ内で使用する

react-redux から提供される Provider コンポーネントを使用します。
App.jsあたりに記述しましょう

App.js

import React from 'react';
import RootStackScreen from './src/navigations/';
import store from './src/redux/store';
import {Provider} from 'react-redux';

const App = () => {
  return (
    <Provider store={store}>
      <RootStackScreen />
    </Provider>
  );
};

export default App;

画面ごとにstateを受け取る

ストアを参照して更新します。まず初めにHomeScreenにstateを受け取るための処理を書きます
そのためには useSelector と useDispatch というフックを使用します

HomeScreen.js

import React from 'react';
import {StyleSheet, View, Text, Button} from 'react-native';
import {getInfo, resetInfo} from '../redux/user/userSlice';
// フックをimport
import {useSelector, useDispatch} from 'react-redux';

const HomeScreen = ({navigation}) => {
  const dispatch = useDispatch();
  // userというstateを参照する
  const user = useSelector(state => state.user);

  return (
    <View style={styles.container}>
      <Text>私の名前は{user.userName}</Text>
      <Text>メールアドレスは{user.userEmail}</Text>
      <Button
        title="セットする"
        onPress={() => {
          // dispatch関数を実行することで該当のreducerが作動する
          dispatch(getInfo());
        }}
      />
      <Button
        title="リセットする"
        onPress={() => {
          dispatch(resetInfo());
        }}
      />
      <Button
        title="画面遷移する"
        onPress={() => {
          navigation.navigate('Detail');
        }}
      />
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
});

export default HomeScreen;

セットするというボタンを押すとTanaka Taroとexample@gmail.comが入って、
リセットするというボタンを押すと空文字(null)になるはずです。

お次はDetailScreenでtodoのstateをもらいましょう
同様に useSelector と useDispatch を使います。

DetailScreen.js

import React from 'react';
import {StyleSheet, View, Text, Button} from 'react-native';
import {setTodo, resetTodo} from '../redux/todo/todoSlice';
import {useSelector, useDispatch} from 'react-redux';

const DetailScreen = ({navigation}) => {
  const dispatch = useDispatch();
  const todo = useSelector(state => state.todo);

  return (
    <View style={styles.container}>
      <Text>明日のTodoは{todo.userTodo}</Text>
      <Button
        title="セットする"
        onPress={() => {
          dispatch(setTodo());
        }}
      />
      <Button
        title="リセットする"
        onPress={() => {
          dispatch(resetTodo());
        }}
      />
      <Button
        title="Home画面に遷移する"
        onPress={() => {
          navigation.navigate('Home');
        }}
      />
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
});

export default DetailScreen;

セットするというボタンを押すとjoggingが入って、
リセットするというボタンを押すと空文字(null)になるはずです。

簡単にですがこれでstoreからstateを受け取ることができました!

最後に

やはりReduxを使うと記述量がどうしても多くなってしまい、複雑化してしまいますよね。
それをなるべく簡単にするのがredux-toolkitです。

ここまで紹介した実装であれば、扱わなければならないsliceが増えてもディレクトリごとにファイルを分けて、reducers.jsの中でimportすれば何かと管理しやすくなるかと思います。

参考

https://www.hypertextcandy.com/learn-react-redux-with-hooks-and-redux-starter-kit
https://future-architect.github.io/articles/20200429/