React入門 - Part4.3 - 親子間コンポーネントへの値の受け渡し - Redux編


<!--
https://zenn.dev/mayamashita/articles/12ab4a122a02cc
https://zenn.dev/asano/articles/c3295b9c30b735
https://www.hypertextcandy.com/learn-react-redux-with-hooks-and-redux-starter-kit
https://www.webopixel.net/javascript/1601.html
https://www.youtube.com/watch?v=2LLinxKgYWg
-->

# 目次

# なぜReduxなのか

[Part4.1](https://qiita.com/yokowake12345/items/effc82bfd0c7b13460ca)や、[Part4.2](https://qiita.com/yokowake12345/items/92ac41a616321d944462)でProps、useContextを学びました。

いずれも表示値や制御値をどこで管理するかということになります。

propsの欠点は、<font color="red">バケツリレー</font>。
バケツリレーをもう少し説明すると、

- 親、子、孫、ひ孫・・・・というコンポーネント階層があり
- 親で状態管理する(変数を表示する)要素があり、ひ孫でそれを更新する処理がある。逆も然り。

このような状況化で、propsで状態を受け渡すのは煩雑であり、また更新するたびに連なっているコンポーネントがレンダリングされることになりパフォーマンス的にも良くない。
（値を受け渡すだけで使用していないコンポーネントでもレンダリングが走るようです。またその対策もあるようですが、propsの中身がオブジェクトの場合はその対策も効かないようです。）

以下のようなイメージです。[※こちらからイメージ画像いただきました。](https://qiita.com/jima-r20/items/4a5d0074e24add0df3fc)

propsの場合、孫①で更新された内容を子①、親、子②を通じて孫③へ伝達します。
Reduxの場合、孫①はStore上の値を更新し、孫③はStore上の値を読み取ります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/559029/b430c469-df6f-647d-2bb8-9b3d19ee3c7c.png)


Reduxは、バケツリレーせずにグローバル値のように値を管理できるということになります。

よく<font color="red">状態管理</font>という言葉が使われますが、私はなんだか馴染めない。。
（教えてください。。）

# Redux、Redux Toolkitについてちょっとだけ

タイトルにRedux編と書いてますが、

1. Reduxは、使い易い感じはするが、ソースが多いので<font color="red">導入時のハードルが高い</font>
2. Redux Toolkitは<font color="red">Reduxよりも導入が容易</font>
3. 2.の為、Reduxより主流になるのではという人もいます。

ということで、Redux Toolkitを導入してみることにしました。

Reduxでは<font color="red">Store</font>という概念で状態管理を行いますが、Redux Toolkitでは、<font color="red">Slice</font>
という概念があるようです。(Sliceという概念の中にStoreを管理してる？)

Reduxは家族を纏めて管理しているが、Redux Toolkitはお父さん、お母さん、長男などを別々に管理している感じとどこかに書いてありました。
※どこかは忘れました。

<font color="red">「State + Actions + Reducer」のセットがsliceとのことです。</font>
Actions、Reducerなどはググってみてください（難しく説明できません。。。雰囲気くらいつかんでおけばいいかと。。。）

# React + Typescript環境を作成

以下コマンドを実行します。

```
npx create-react-app プロジェクト名 --template redux-typescript
```

以下コマンドを実行し初期画面が表示されることを確認します。

```
cd プロジェクト名
yarn start
```

初期画面です。
※下記画面が表示されない場合、ブラウザで`localhost:3000`にアクセスしてみてください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/559029/9a723947-a235-0cc5-aef2-ed88c40c7c65.png)

# Todoアプリ用のフォルダとファイルを作成します。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/559029/8871587c-f2b2-3043-4355-0af357a71843.png" width="60%" />

featuresディレクトリの下に`todo`ディレクトリを作成し、その下に以下のファイルを作成します。

- Todo.ts
- TodoInput.tsx
- TodoItem.tsx
- TodoList.tsx
- todoSlice.ts

# Todo.ts

Todoの型を定義します。

```typescript
export type Todo = {
  id: number;
  title: string;
  done: boolean;
};
```

# todoSlice.ts

Todoに関するAction、State、Reducerを定義します。
redux(-toolkit)の根幹でしょう。

```typescript
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
import { Todo } from "./Todo";

// Todo管理の型定義
type State = {
  count: number;
  todos: Todo[];
};

// Todoの初期データ
const initialState: State = {
  count: 3,
  todos: [
    {
      id: 3,
      title: "神を切る",
      done: false,
    },
    {
      id: 2,
      title: "葉を洗う",
      done: false,
    },
    {
      id: 1,
      title: "顔を磨く",
      done: true,
    },
  ],
};

// createSlice
export const todoSlice = createSlice({
  name: "todo",
  initialState,
  reducers: {
    addTodo(state: State, action: PayloadAction<string>) {
      state.count++;

      const newTodo: Todo = {
        id: state.count,
        title: action.payload,
        done: false,
      };

      // 既存タスクに追加
      state.todos = [newTodo, ...state.todos];
    },

    doneTodo(state: State, action: PayloadAction<Todo>) {
      // 該当タスクを探し、doneフラグを更新する
      const todo = state.todos.find((t) => t.id === action.payload.id);
      if (todo) {
        todo.done = !todo.done;
      }
    },

    deleteTodo(state: State, action: PayloadAction<Todo>) {
      // 該当タスクを除くタスクを、全タスクとする
      state.todos = state.todos.filter((t) => t.id !== action.payload.id);
    },
  },
});
export const { addTodo, doneTodo, deleteTodo } = todoSlice.actions;

export default todoSlice.reducer;
```

# app/store.tsを修正

既存のstore.tsに先ほど書いた`todoSlice`を利用できるように追加します。

```typescript
import { configureStore, ThunkAction, Action } from "@reduxjs/toolkit";
import counterReducer from "../features/counter/counterSlice";
import todoReducer from "../features/todo/todoSlice"; // 追加

export const store = configureStore({
  reducer: {
    counter: counterReducer,
    todo: todoReducer, // 追加
  },
});

export type AppDispatch = typeof store.dispatch;
export type RootState = ReturnType<typeof store.getState>;
export type AppThunk<ReturnType = void> = ThunkAction<
  ReturnType,
  RootState,
  unknown,
  Action<string>
>;
```


```typescript
npx create-react-app プロジェクト名 --template typescript
```

# TodoItem.tsx

Todo1アイテムを表示するコンポーネントを書きます。

- todoのdone状態を変更するチェックボックスを持ちます。
- todoのタイトルを表示します。
- 削除ボタンを持ちます。
- チェックボックスは、done状態を変更するActionをDispatchします。
- 削除ボタンは、タスクを削除するActionをDispatchします。

```typescript
import { Todo } from "./Todo";
import { useDispatch } from "react-redux";
import { doneTodo, deleteTodo } from "./todoSlice";

type Props = {
  todo: Todo;
};

const TodoItem: React.FC<Props> = ({ todo }) => {
  const dispatch = useDispatch();

  return (
    <li>
      <label>
        <input
          type="checkbox"
          onClick={() => dispatch(doneTodo(todo))}
          defaultChecked={todo.done}
        />
        <span className="checkbox-label">{todo.title}</span>
      </label>
      <button onClick={() => dispatch(deleteTodo(todo))}>削除</button>
    </li>
  );
};

export default TodoItem;
```

# TodoList.tsx

現在保持しているtodoを全て表示するコンポーネントを書きます。

```typescript
import React from "react";
import TodoItem from "./TodoItem";
import { useSelector } from "react-redux";
import { RootState } from "../../app/store";
import { Todo } from "./Todo";

const TodoList: React.FC = () => {
  const { todos } = useSelector((state: RootState) => state.todo);

  return (
    <div>
      {todos.length <= 0 ? (
        "登録されたTODOはありません。"
      ) : (
        <ul>
          {todos.map((todo: Todo) => (
            <TodoItem key={todo.id} todo={todo} />
          ))}
        </ul>
      )}
    </div>
  );
};

export default TodoList;
```

# TodoInput.tsx

todoを新規追加するコンポーネントを書きます。

- todoタイトルを入力するテキストボックスがあります。
- todoを追加するボタンがあり、todoを追加するActionをDispatchします。

```typescript
import React, { useState } from "react";
import { useDispatch } from "react-redux";
import { addTodo } from "./todoSlice";

const TodoInput: React.FC = () => {
  const dispatch = useDispatch();

  const [inputTitle, setInputTitle] = useState("");

  const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    setInputTitle(e.target.value);
  };

  const handleSubmit = () => {
    dispatch(addTodo(inputTitle));
    setInputTitle("");
  };

  return (
    <div>
      <input
        type="text"
        value={inputTitle}
        onChange={handleInputChange}
        placeholder="TODOを入力してください。"
      />
      <button onClick={handleSubmit}>追加</button>
    </div>
  );
};

export default TodoInput;
```

# App.tsxを書きに書き換える

作成したコンポーネントを読み込むように設定します。

```typescript
import React from "react";
import TodoList from "./features/todo/TodoList";
import TodoInput from "./features/todo/TodoInput";
import "./App.css";

const App: React.FC = () => {
  return (
    <div>
      <TodoInput />
      <TodoList />
    </div>
  );
};

export default App;
```


# 実食

以下のような画面になると思います。
追加、削除やチェックボックスの動作確認をしてみてください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/559029/fdb311ae-51c3-09f8-8060-4604df8029e6.png)


# ちょっと休憩

用語の意味をちょっとだけ考えてみましょう。

◆reducer
〔+目的語+to+(代)名詞〕〈ものを〉(整理して)〔簡単な形に〕変える，まとめる.
　⇒「状態とアクションを受け取って、次の状態を返すやつ」

◆action
アクションのタイプ(必須)とstate(値)のみ持つプレーンなオブジェクト

以下のようなイメージになります。

```typescript
const action = {
  type: 'ADD_TODO',
  text: 'Go to swimming pool'
};
```

storeによって、actionとstateがreducerに渡され、reducerで処理が行われます。

◆store
ReducerとStateにアクセスできるオブジェクト

◆概念
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/559029/56e046c0-a56f-d9e8-81f4-7cf331c12dbf.png)

reduxを調べるとこんな図がよく出てきます。
ふむふむよくわかる。。って分かりませんよね。
最初はよく分かんなくていいと思います。（勝手。）
私も概念はまだよく理解していませんが、

reduxを利用するには、

ActionとかStoreを決まったように書いて(あるものをコピペして名前変えて)
って使ってます。

でも最初にredux使える環境を作る時には困りますかね。。。。

# Reduxの例え話

例え話を考えてみました。

Reduxの例え話を考えてみた。

**物（Redux state）**

実際の物はお店（store）にあります。

```
・テキストＡ原本
・テキストＢ原本
```

**指示看板（action type）と指示に使用する値**

```
指示看板（テキストＡに追記して）
   ＋
値：２

とか

指示看板（テキストＡを上書きして）
   ＋
値：１
```

**お店（store)**

```
お店には、指示看板を処理する変更人（reducer）って人がいます。
```

**dispatch**

```
指示看板（action type）と指示に使用する値、つまりactionを
お店に送ります(dispatch)します。
```

```
お店にいる変更人（reducer）は  
指示看板（action type）と指示に使用する値、つまりactionを元に
新しい値に変更します。
```

**selector**

```
後日、自分の子会社(子コンポーネント)がお店(store)に対して、テキストAの印刷したやつくれないかと言ってもらう(selector)
```

どうですか。
苦しいですかね。