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

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

# 目次
* [Reduxってなに？](#reduxってなに)
* [ReactにおけるRedux使用のメリット](#reactにおけるredux使用のメリット)
* [Redux - 3つの原則 -](#redux---３つの原則--)
* [Redux構造](#redux構造)
* [コンポーネントによる記述の違い](#コンポーネントによる記述の違い)

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

<details>
<summary>fluxとは</summary>
<div>
facebook社が作成した、単一方向のデータフローを採用したアーキテクチャです。
全体のデータはStoreで管理され、データの更新は必ずActionを受け取ってDispatcherを経由する必要がある為、アプリケーションを安全で予測しやすい状態に保つことができます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2534134/3145bf4f-5b2e-d31e-f93f-b0f2f35a457f.png" />
公式より引用：<a href="https://github.com/facebookarchive/flux/tree/main/examples/flux-concepts">https://github.com/facebookarchive/flux/tree/main/examples/flux-concepts</a>
</div>

</details>


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

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2534134/093e17ed-f158-1668-39ee-cc4bf077d64b.png" width="70%">


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

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2534134/f3accd01-7f8f-1de6-a54d-583d7ccbac12.png" width="70%">


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

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

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

![スクリーンショット 2023-12-15 011005.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2534134/f6972e87-9999-091e-23a0-a1a1f6f986a5.png)



<font color="#666">基本のデータの流れ</font>
 
1. ユーザーのクリックや入力等の操作があった場合、  ActionCreatorsによってActionが生成される  
2. 生成されたActionをStoreへ送信（Dispatch）する												
3. ReducerがActionを受け取り、Store内の既存stateと組み合わせて新しい状態に更新する  
4. UIは新しい状態を読み取り、更新された値を表示する													

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

### Action
![スクリーンショット 2023-12-15 015357.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2534134/864e06c7-40f4-fdd3-759f-713b0ff638d8.png)



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

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

通常、下記のようなActionオブジェクトを返す`ActionCreators関数`を作成して、Actionを呼び出します。
``` js
 export const increase = () => {
   return {
     type: "INCREASE",
     payload: 2
   };
 };								
```

### Dispatch
![スクリーンショット 2023-12-15 015244.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2534134/94ea80da-9236-0b04-48f1-6646fd0fcae5.png)



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

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

```

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

### Reducer
![スクリーンショット 2023-12-15 015131.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2534134/fa2b4235-8b3e-830d-9fa4-673b5af68cac.png)



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

```js

 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をコピーし、コピーした値を更新することによって`不変の更新`を行う必要がある。


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

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

 3\. 非同期ロジックやその他の「副作用」を実行してはいけない。  
reducerは`特定の入力が渡されたときに特定の値を返す純粋関数`である為、`副作用の実行はできない`。
 
:::note warn
副作用例														
コンソールログへの出力																	
DOM操作 						
サーバーとの通信
ランダムな値の生成								　　　　　　				
タイマー処理																	
:::  

副作用の処理は、[MiddleWare](#middleware)に記載しましょう。


### Store
![スクリーンショット 2023-12-15 014939.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2534134/820fc9fb-b5fc-3e54-1b83-6b7cc438fe98.png)


Storeはstateを管理する1つの`保管場所`のようなものです。
`createStore`でStoreを作成することができます。  
```	js
 import { createStore } from "redux";																							
 
 const store = createStore(counterReducer);	// 作成したreducerをimport	
 ```

 reducerが複数の場合は、`combineReducers`で１つに収集することで、storeで扱うことができます。
 ``` js
  import { combineReducers } from "redux";														

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

現在では`ReduxToolkit`[^3]を用いた実装が推奨されており、storeの作成は、`configureStore`が使用されることが多いです。configureStoreの場合は、複数のReduceを扱う時も、自動でまとめてくれる為、combineReducersの使用は不要です。
[^3]:ReduxToolKit：https://redux-toolkit.js.org/
Redux-ToolKitとは、Reduxを用いた開発を効率的に行うためのツールキットです。							素のReduxでは機能が足りない部分があるため、Immerやredux-thunk,createSliceなどのライブラリが使用されますが、これらのライブラリを丸ごと同封しています。																																					

```js																				
 // 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を繋げてくれる役割`を果たしています。

 
 ```js	
 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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2534134/3e6fc06c-2a8c-532b-9e6b-11a6c8c3f69a.png)




hooksの`useSelector`を使用することで、`stateの値を取得`することができます。  
state取得後、コンポーネントが再レンダリングされることで、ユーザーが更新された値を確認することができます。
 
```js
 import { useSelector } from "react-redux";

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

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

 export default Counter;
															
```


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

 <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2534134/d8953994-bdbb-88e5-0a51-5e1569a22131.png" width="90%">

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

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


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

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

:::note info
<font color="#555">2つの記述の主な違い</font>
<font>1. stateを取得する</font>																
    mapStateToProps() → useSelector()
    
<font>2. Actionをdispatchする</font>
    mapDispatchToProps() → useDispatch()
    
<font>3. componentとStoreを連携させる</font>
    connect() → 不要
:::

#### stateの取得
クラスコンポーネントではmapStateToProps、関数コンポーネント+hooksでは、「[useSelector()](#selector)」を使用します。

* クラスコンポーネントの場合
```	js
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),
   };
 };
 ```

 * 関数コンポーネントの場合
 ``` js
 import { useSelector } from "react-redux";
 import { getLanguage } from "./selectors";

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

   return(
    //jsx
   )
 };

export default Sample;
 ```

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

 * クラスコンポーネントの場合
 ``` js
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);
};

```

* 関数コンポーネントの場合
``` js
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関数に`state`と`dispatch`をわたすことで、コンポーネントはこれらを`props`として受けることができます。storeの状態に変更があった場合はpropsのstateを自動で更新し、propsのdispatch関数が呼び出された時には、自動でdispatchを行ってくれます。
</div>
</details>　　

* クラスコンポーネント

```js
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: