Redux最低限（2021年8月）


もう今後はRecoilかな。。。とは思いますが、Reduxも他人のソース見たりするときに必要なので書いておきます。
Recoil版は[こちら](https://qiita.com/zaburo/items/e28be44f6b98e36b38c7)。

## やりたいこと

* hook利用下でのRedux利用
* react-router-domを利用したページ遷移での値の維持確認
* データの永続化

下記のような感じ。

![スクリーンショット 2021-08-31 19.58.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/55188/62c7ebe7-aaf7-bce7-0137-08cd1a012148.png "スクリーンショット 2021-08-31 19.58.35.png")


## 準備

まず準備。

### 作業場の確保とライブラリのインストール

```bash
npx create-react-app redux-test
cd redux-test

npm install redux react-redux react-router-dom
```

## Reduxとは関係のない実装（骨組みづくり）

まず、複数ページからなる最低限の構造を作ります。とりあえずHomeとAboutからなるサイトにしてみます。

src以下にHome.jsとAbout.jsを作ります。

```bash
touch src/Home.js src/About
```

Home.jsを記述。

```js:Home.js
import { Link } from "react-router-dom";

const Home = () => {
    return (
        <>
            <h1>Home</h1>
            <Link to="/about">About</Link>
        </>
    );
}

export default Home;
```

>移動には\<a>\</a>ではなく\<Link>\</Lin>を利用してください。じゃないと、ページが更新されrecoilによる値の維持ができません。

About.jsを記述。

```js:About.js
import { Link } from "react-router-dom";

const About = () => {
    return (
        <>
            <h1>About</h1>
            <Link to="/">Home</Link>
        </>
    );
}

export default About;
```

作成したHome.jsとAbout.jsを読み込んでサイトの構造を作ります。

```js:App.js
import { BrowserRouter, Switch, Route } from "react-router-dom";

import Home from "./Home";
import About from "./About";

const App = () => {
  return (
    <BrowserRouter>
      <Switch>
        <Route path="/" exact component={Home} />
        <Route path="/about" exact component={About} />
        <Route render={() => <p>Page not found.</p>} />
      </Switch>
    </BrowserRouter>
  );
}
export default App;
```

一度、動作を確認して置きましょう。

```bash
npm start
```

ページ間を移動する簡単なサイトができました。
Recoilを利用して、このページ間で状態を維持するようにしてみたいと思います。

![スクリーンショット 2021-08-31 6.51.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/55188/ef1fbe43-55ac-b9e4-87dd-1dc3786e82c5.png "スクリーンショット 2021-08-31 6.51.59.png")

## Reduxを利用した実装

ここからが本題です。

### Reducerを作っておく

src以下にreducersというディレクトリを作成し、その下にrootReducer.jsを作成します。
初期値は変数とオブジェクトを用意。countとuser.ageをカウントアップするactionを用意。

```js:src/reducers/rootReducer.js
//初期値
const initialState = {
    count: 0,
    user: {
        name: "hoge",
        age: 11
    }
}

//reducer
export const reducer = (state = initialState, action) => {
    switch (action.type) {
        case "count_increment":
            return { ...state, count: state.count + 1 };
        case "age_increment":
            //deep copy
            const newState = JSON.parse(JSON.stringify(state));
            newState.user.age = state.user.age + 1;
            return newState;
        default:
            return state;
    }
}
```

### Reduxで状態を共有したエリアを囲む

RecoilでもContextでも値を共有したいエリアを専用タグで囲みました。ReduxではProviderです。
多くの場合、サイト全体で値を共有するのでindex.jsに記述します。

```js:index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import reportWebVitals from './reportWebVitals';

//redux関連読み込み
import { createStore } from "redux";
import { Provider } from "react-redux";

//reducer読み込み
import { reducer } from "./reducers/rootReducer";

//グローバルでの値保管場所生成
const store = createStore(reducer);

ReactDOM.render(
  <React.StrictMode>
    <Provider store={store}>
        <App />
    </Provider>
  </React.StrictMode>,
  document.getElementById('root')
);

reportWebVitals();
```

### Home.jsの編集（値の利用と更新）

Homeにstate値の表示と編集用の関数を設置。
値はuseSelector()で、関数はdispatchで利用する。connectしなくていいのはいいですね。

```js:Home.js
import { useSelector, useDispatch } from "react-redux";
import { Link } from "react-router-dom";

const Home = () => {

    //state（値）の利用
    const count = useSelector(state => state.count);
    const user = useSelector(state => state.user);
    //dispatch（値編集関数）の利用
    const dispatch = useDispatch();

    //counterを加算
    const incrementCount = () => {
        dispatch({ type: "count_increment" });
    }

    //ageを加算
    const incrementAge = () => {
        dispatch({ type: "age_increment" });
    }

    return (
        <>
            <h1>Home</h1>
            <p>count:{count}</p>
            <button onClick={() => incrementCount()}>increment</button>
            <hr />
            <p>user.name:{user.name}</p>
            <p>user.name:{user.age}</p>
            <button onClick={() => incrementAge()}>increment user age</button>
            <br/>
            <br/>
            <Link to="/about">About</Link>
        </>
    );
}

export default Home;
```

### About.js

続いてAbout。とりあえず表示だけ。

```js:About.js
import { useSelector } from "react-redux";
import { Link } from "react-router-dom";

const About = () => {

    //state（値）の利用
    const count = useSelector(state => state.count);
    const user = useSelector(state => state.user);

    return (
        <>
            <h1>About</h1>
            <p>count:{count}</p>
            <hr />
            <p>user.name:{user.name}</p>
            <p>user.name:{user.age}</p>
            <br/>
            <br/>
            <Link to="/">Home</Link>
        </>
    );
}

export default About;
```

## 値の永続化（Persist）

実用的なアプリを開発するにはデータの永続化は不可欠ですのでやり方を検証しておきます。

### モジュールの追加

あまりメンテされてるモジュールがないみたい。とりえあえずredux-persist使う。

```bash
npm install redux-persist
```

redux-persistのページで紹介されている例を参考にconfigureStore.jsを作成。
ここで単純なstoreではなく、persist対応のstoreを作成。userだけ永続化する（countはしない）。

### configureStore.jsの作成

```js:src/configureStore.js
import { createStore } from 'redux'
import { persistReducer, persistStore } from 'redux-persist'
import storage from 'redux-persist/lib/storage'

//reducer読み込み
import { reducer } from "./reducers/rootReducer";

//config
const persistConfig = {
    key: 'root',
    storage,
    whitelist: ['user'] //whitelist この場合、userのみ永続化される（countはされない）
}

const persistedReducer = persistReducer(persistConfig, reducer);

const store = createStore(
    persistedReducer,
);

export const persistor = persistStore(store)
export default store
```

### index.jsの編集

index.jsを変種してpersist対応できるコードに変更。

```js:index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import reportWebVitals from './reportWebVitals';

//redux
import { Provider } from "react-redux";

//persist
import { PersistGate } from 'redux-persist/integration/react'
//configureStoreの読み込み
import store, { persistor } from './configureStore'

ReactDOM.render(
  <React.StrictMode>
    <Provider store={store}>
      <PersistGate loading={null} persistor={persistor}>
        <App />
      </PersistGate>
    </Provider>
  </React.StrictMode>,
  document.getElementById('root')
);

reportWebVitals();
```

動作確認してみると、user.ageの値は残るが、countは永続化されていないことが確認できました。