29
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

株式会社VISIONARY JAPANAdvent Calendar 2023

Day 16

初心者向け - React Redux 基礎まとめ -

Last updated at Posted at 2023-12-15

今回Reactのプロジェクトで、Reduxを使用する機会があり、自分の備忘録も兼ねて、Reduxの基礎部分で学んだことをまとめてみました。

目次

Reduxってなに?

Reduxとは、fluxのアーキテクチャ概念に基づいて構成されている状態管理ライブラリです。
Reduxを使用することで、アプリケーション全体でデータを一元管理することができ、コンポーネント間のデータのやり取りをスムーズに行うことができます。
AngularやJQueryなどのほかのJavaScriptライブラリと使用することも可能で、Reactで使用する場合は、Reduxライブラリに加えて、react-reduxのライブラリのインストールが必要となります。

fluxとは
facebook社が作成した、単一方向のデータフローを採用したアーキテクチャです。 全体のデータはStoreで管理され、データの更新は必ずActionを受け取ってDispatcherを経由する必要がある為、アプリケーションを安全で予測しやすい状態に保つことができます。 公式より引用:https://github.com/facebookarchive/flux/tree/main/examples/flux-concepts

ReactにおけるRedux使用のメリット

Reactでは親から子コンポーネントへstateを渡したい場合、バケツリレーのように上から順にデータを受け渡す必要がありました。親→孫→子の階層が深くなるほど、データの受け渡しは複雑になります。また、親子間を通さずに、別の階層のコンポーネントのstateを参照する操作も通常行うことができません。

Reduxを使用すると...
Reduxはstateを一元管理する保管場所のような「store」を保持しているので、storeにあるstateは全コンポーネントから参照することができ、バケツリレーを経由せず、storeから直接stateを取得することができます。

Redux - 3つの原則 -

1. 信頼できる唯一の情報源 
・アプリケーションの状態は、単一のストアのオブジェクトツリーで保存されます。
1つのstoreで状態が管理されることによって、アプリケーションのデバッグや調査が容易になります
2. 状態は読み取り専用
・状態を変更する手段は、変更の情報を持つActionを発行する場合のみ
変更はデータフローに従って、順序立てて実行されるため、変更の衝突などがなくなります

3. 変更は純粋な関数で行われる
・変更は前の状態とアクションを取得して次の状態を返す純粋なReducer関数によって行われます。状態を更新する場合は、以前の状態の書き換えを行うのではなく、新しいオブジェクトを返すようにしなければいけません。

Redux構造

Reduxの基本のデータの流れは下記のようなイメージになります。

スクリーンショット 2023-12-15 011005.png

基本のデータの流れ

  1. ユーザーのクリックや入力等の操作があった場合、 ActionCreatorsによってActionが生成される
  2. 生成されたActionをStoreへ送信(Dispatch)する
  3. ReducerがActionを受け取り、Store内の既存stateと組み合わせて新しい状態に更新する
  4. UIは新しい状態を読み取り、更新された値を表示する

各役割を1つずつ細かくみていきます。

Action

スクリーンショット 2023-12-15 015357.png

Actionは、Actionの種類(type)変更に必要な付加情報(payload)を持つオブジェクトです。
ユーザーが入力やクリックなどの操作を行うことで、Actionが生成されます。

 {
   type: "INCREASE",
   payload: 2
 };

通常、下記のようなActionオブジェクトを返すActionCreators関数を作成して、Actionを呼び出します。

 export const increase = () => {
   return {
     type: "INCREASE",
     payload: 2
   };
 };								

Dispatch

スクリーンショット 2023-12-15 015244.png

DispatchはActionを受け取って、storeへ送る役割を果たしています。
hooksのuseDispatchを呼び出して、dispatch関数を使用することができます。

 import { useDispatch } from 'react-redux';																		
																		
 const dispatch = useDispatch();																	
																		
 const countUp = () => {
    dispatch(increase());  // 引数でActionを受け取る 
 };                        // ここでは1つ前で説明したActionCreatorsを呼び出しています)

 <button onClick={countUp}>countUp</button>

Reducer

スクリーンショット 2023-12-15 015131.png

Reducerは、受け取ったActionの種類(type)オプションの情報(payload)によって、stateを更新します。引数でstate(更新前)とactionを受け取り、state(更新後)を返します。


 export const counterReducer = (state = 0, action) => {
   switch (action.type) {
       case "INCREASE":
       return state + action.payload;
       case "DECREASE";
       return state - action.payload;
       default;
       return state;
    }
 };		

- Reducerの特別なルール -

1. 以前のstateとactionオブジェクトに基づいて新しいstateのみを計算する。
2. 既存のstateを更新してはいけない。
代わりにstateをコピーし、コピーした値を更新することによって不変の更新を行う必要がある。

 // ❎ 直接の更新				
 state.value = 123;

 // ✅ コピーした値の更新
 return {
   ...state,
   value: 123
 }			

3. 非同期ロジックやその他の「副作用」を実行してはいけない。
reducerは特定の入力が渡されたときに特定の値を返す純粋関数である為、副作用の実行はできない

副作用例
コンソールログへの出力
DOM操作
サーバーとの通信
ランダムな値の生成       
タイマー処理

副作用の処理は、MiddleWareに記載しましょう。

Store

スクリーンショット 2023-12-15 014939.png

Storeはstateを管理する1つの保管場所のようなものです。
createStoreでStoreを作成することができます。

 import { createStore } from "redux";																							
 
 const store = createStore(counterReducer);	// 作成したreducerをimport	

reducerが複数の場合は、combineReducersで1つに収集することで、storeで扱うことができます。

 import { combineReducers } from "redux";														

 const reducers = combineReducers({  
   counter: reducer,	
   todo: todoReducer														
 });														
   													
 export default reducers;														

現在ではReduxToolkit1を用いた実装が推奨されており、storeの作成は、configureStoreが使用されることが多いです。configureStoreの場合は、複数のReduceを扱う時も、自動でまとめてくれる為、combineReducersの使用は不要です。

 // redux-toolkitの場合																 
 import { configureStore } from "@reduxjs/toolkit";

 const store = configureStore({
   reducer: {
     count: counterReducer,
     todo: todoReducer
   }
 });

export default store;

- Storeの呼び出し -

Providerをimportし、AppコンポーネントをProviderで囲い、作成したstoreを渡します。
Providerは、storeとstateを繋げてくれる役割を果たしています。

import Counter from "./components/Counter";
import { Provider } from "react-redux";
import store from "./store";

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

export default App;

Selector

スクリーンショット 2023-12-15 014755.png

hooksのuseSelectorを使用することで、stateの値を取得することができます。
state取得後、コンポーネントが再レンダリングされることで、ユーザーが更新された値を確認することができます。

 import { useSelector } from "react-redux";

 const Counter = () => {
   const count = useSelector((state) => state.count);

   return <div>{count}</div>;
 };

 export default Counter;
															

- Redux 非同期処理 -

Reduxで非同期処理を行う場合に、必要になってくるのがmiddlewareの存在です。
非同期処理を行う場合のRedux構造は下記のような図のイメージになります。

Middleware

非同期処理はstore内で実行することはできない為、非同期処理はstoreの外側で実行する必要があります。非同期処理と現在のstoreの状態を繋げてくれる役割を果たしているのが、middleWareです。reduxで使用されているmiddlewareは多くありますが、最も使用されているものが、redux-thunkです。

redux-thunk

ActionCreatorはActionオブジェクトを返しますが、redux-thunkの場合は、thunk関数を返します。
これによって、Actionのdispatchを遅らせることができたり、特定の条件の場合にdispatchを行うなどの処理が可能になります。thunk関数内では、引数のdispatchgetStateの使用ができます。

 export const addAsync = (payload) => {
   return async (dispatch, getState) => {
     // stateの取得 
     const state = getState();
     
     const response = await asyncCount(payload);
     // dispatchの使用
     dispatch(add(response.data)); 
   };
 };

コンポーネントによる記述の違い

現在では関数コンポーネントによる記述が多いですが、今回のプロジェクトではクラスコンポーネントによるReduxの記述だった為、2つの記述の違いについても触れたいと思います。

2つの記述の主な違い
1. stateを取得する
mapStateToProps() → useSelector()

2. Actionをdispatchする
mapDispatchToProps() → useDispatch()

3. componentとStoreを連携させる
connect() → 不要

stateの取得

クラスコンポーネントではmapStateToProps、関数コンポーネント+hooksでは、「useSelector()」を使用します。

  • クラスコンポーネントの場合
import { getLanguage } from "./selectors";

 class Sample extends Component {
   componentDidMount() {
     this.props.setLanguageAction(this.props.language);
   }

   render() {
     // jsx
   }
 }

 // mapStateToProps の追加
 const mapStateToProps = (state) => {
   return {
     language: getLanguage(state),
   };
 };
  • 関数コンポーネントの場合
import { useSelector } from "react-redux";
import { getLanguage } from "./selectors";

const Sample = () => {
  const language = useSelector(getLanguage);

  return(
   //jsx
  )
};

export default Sample;

Dispatch

クラスコンポーネントでは「mapDispatchToProps」、関数コンポーネント+hooksでは「useDispatch()」を使用することができます。

  • クラスコンポーネントの場合
import { getLanguage } from "./selector";
import { setLanguageAction } from "./actions";

class Sample extends Component {
 componentDidMount(){
   this.props.setLanguageAction(this.props.language);
 }

 render() {
   // jsx
 }
}

// mapDispatchToProps の追加
const mapDispatchToProps = (dispatch) => {
 //bindActionCreatorは通常のActionの呼び出しを簡潔にしてくれるもの
 return bindActionCreators({ setLanguageAction }, dispatch);
};

  • 関数コンポーネントの場合
import { useSelector, useDispatch } from "react-redux";
import { getLanguage } from "./selectors";
import { setLanguageAction } from "./actions";

const Sample = () => {
  const language = useSelector(getSelectedLanguage);
  const dispatch = useDispatch();

  useEffect(() => { 
    dispatch(setLanguageAction(language)); 
  }, []);

  return (
   //jsx
  );
};

export default Sample;

componentとstoreの連携

クラスコンポーネントでは、storeとコンポーネントを接続する為にconnect関数を使用します。関数コンポーネントの場合は不要です。

- connect -
connect関数にstatedispatchをわたすことで、コンポーネントはこれらをpropsとして受けることができます。storeの状態に変更があった場合はpropsのstateを自動で更新し、propsのdispatch関数が呼び出された時には、自動でdispatchを行ってくれます。

  
  • クラスコンポーネント
import { getLanguage } from "./selector";
import { setLanguageAction } from "./actions";

class Sample extends Component {
  componentDidMount(){
    this.props.setLanguageAction(this.props.language);
  }

  render() {
    // jsx
  }
}

const mapStateToProps = (state) => {
  return {
    language: getLanguage(state),
  };
};

const mapDispatchToProps = (dispatch) => {
  return bindActionCreators({ setLanguageAction }, dispatch);
};

// storeとコンポーネントを接続
export default connect(mapStateToProps, mapDispatchToProps)(Sample);

ひとつ前の関数コンポーネントの場合と比較して分かるように、関数コンポーネント+hooksの使用で、より簡潔にReduxの記述を行うことができるようになりました。

最後に

超入門な記事になりましたが、最後まで見てくださり有難うございました!
2023年も残り僅か。。みなさま今年もお疲れ様でした :santa_tone1: :christmas_tree: :night_with_stars:

  1. ReduxToolKit:https://redux-toolkit.js.org/
    Redux-ToolKitとは、Reduxを用いた開発を効率的に行うためのツールキットです。 素のReduxでは機能が足りない部分があるため、Immerやredux-thunk,createSliceなどのライブラリが使用されますが、これらのライブラリを丸ごと同封しています。

29
5
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
29
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?