TypeScript でRedux を扱う

# イントロダクション

## Redux とは何か、TypeScript と Redux の組み合わせの利点について簡単に説明

この記事では、TypeScript 初心者が Redux を使えるようにするための内容を記載しています。覚書のような内容になるので、お急ぎの方は参考元の動画をご覧ください。

参考元：[ React Redux Toolkit with TypeScript](https://youtu.be/EqbwHO6Vgbg?si=qW-Y2A7hbINXA5yQ)

## 作成したデータ
https://github.com/DaijiroNozaki-afk/redux-ts

### Redux とは

Redux は JavaScript の状態管理ライブラリで、アプリケーションの状態を一元管理し、予測可能な方法で操作するのに役立ちます。

Redux を使わずにデータを扱う場合、React アプリケーション内でデータをリレー形式でコンポーネントからコンポーネントへ渡す必要があります。

### TypeScript と Redux の組み合わせの利点

TypeScript で型安全を確保し、Redux でアプリケーションの状態を一元管理することで、データの変更が予測可能で、バグを特定しやすく、デバッグが容易になります。また、Redux はアプリケーションのロジックを分離して、コードの可読性が向上します。

# 前提知識

この記事を読むには、Redux を使ってデータを利用すること、および TypeScript の基礎知識が必要になります。

また、開発には VSCode を用います。お使いのパソコンには、あらかじめ Node.js をインストールしておいてください。

# Redux の基本

## Redux の基本的な概念の説明

Redux はアプリケーションの状態を一元管理します。
Redux の要素と相関図は次のようになります。

- Action
- ActionCreator
- Store
- State
- Reducer

![redux.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3570094/93e56ed8-6525-c3f1-09dd-c4520dc1498e.png)


## ステート、アクション、リデューサーについて

### State ステート

Redux でアプリケーションの状態を保持するデータです。Immutable で、アプリ全体で共有します。

### Action アクション

イベントやデータを表すオブジェクトで、State を変更するリクエストを出します。タイプとデータを持ちます。

### Reducer リデューサー

Action を受け取り、State を変更する単純な関数です。前の State と Aciton から、新しい State を生成します。

### Immutable なデータとは？

変更できないデータを指します。Redux で保持されているデータは Immutable なので、データを更新するには Aciton を使用する必要があります。

# Redux Toolkit の導入

## Redux Toolkit とは何か

Redux Toolkit は、Redux の公式ライブラリで、Redux 開発を簡素化し、Immutable データ更新、自動コード生成、デバッグをサポートします。

# プロジェクトのセットアップ

## Create React App を使用して React プロジェクトをセットアップ

「redux-ts」というディレクトリを作成し、Create React App を使った React プロジェクトをセットアップします。

前提条件：

- Node.js と npm (Node Package Manager) がインストールされていること。
- Visual Studio Code (VS Code) がインストールされていること。

### 手順

1:ターミナルを開いて、「redux-ts」を作り、ディレクトリに移動します。以下のコマンドを使用してディレクトリに移動します。

```bash
mkdir redux-ts
cd redux-ts
```

2:Create React App をグローバルにインストールしていない場合は、次のコマンドを使用してインストールします。

```lua
npm install -g create-react-app
```

3:Create React App を使用して、新しい React プロジェクトをセットアップします。以下のコマンドを実行します。

```lua
npx create-react-app ./
```

これにより、「redux-ts」内に新しいプロジェクトが作成されます。

4:React プロジェクトが作成されたら、次のコマンドを使用して VS Code を開きます。

```lua
code .
```

5:VS Code 内で、「redux-ts」ディレクトリを開いて React アプリケーションを編集できるようになります。

## Redux Toolkit をプロジェクトに統合

必要なライブラリをインストールします。コマンドラインでプロジェクトのルートディレクトリに移動し、以下のコマンドを実行します。

```lua
npm i react-redux @reduxjs/toolkit
```

## ディレクトリ構造

ディレクトリ構造を次のようにして、サンプルを構築します。

```
src
├components
│├Add.tsx
│└List.tsx
├store
│├features
││└personSlice.ts
│└store.ts
├App.tsx
└index.tsx
```

### src/components/Add.tsx:

ユーザーが新しい要素を追加するためのコンポーネント。新しいデータの入力フィールドと「追加」ボタンが含まれています。useAddDispatch を使ってデータを更新します。

```tsx:Add.tsx
import React, { useRef, MouseEvent } from 'react';
import { useAddDispatch } from '../store/store';
import { addPerson } from '../store/features/personSlice';

const Add = () => {
  const name = useRef<string>('');
  const age = useRef<number>(0);
  const dispatch = useAddDispatch();
  const handleSubmit = (e: MouseEvent<HTMLButtonElement>) => {
    e.preventDefault();
    // age.currentが空の時、0を代入する
    if (!age.current) {
      age.current = 0;
    }
    dispatch(addPerson({ name: name.current, age: age.current }));
  };
  return (
    <form>
      <div>
        <label htmlFor="">Person Name:</label>
        <input onChange={(e) => (name.current = e.target.value)} />
      </div>
      <div>
        <label htmlFor="">Person Age:</label>
        <input
          type="number"
          placeholder="年齢"
          pattern="^[0-9]+$"
          onChange={(e) => (age.current = parseInt(e.target.value))}
        />
      </div>
      <button onClick={handleSubmit}>追加</button>
    </form>
  );
};

export default Add;

```

### src/components/List.tsx:

データリストを表示するコンポーネント。Redux の状態から useAppSelector を使ってデータを取得し、リストとして表示します。

```tsx:List.tsx
import React from 'react';
import { useAppSelector } from '../store/store';

const List = () => {
  const persons = useAppSelector((state) => state.person.persons);
  return (
    <div>
      <p>This is List Components</p>
      <table>
        <thead>
          <tr>
            <th>ID</th>
            <th>Name</th>
            <th>Age</th>
          </tr>
        </thead>
        <tbody>
          {persons.map((person) => (
            <tr key={person.id}>
              <td>{person.id}</td>
              <td>{person.name}</td>
              <td>{person.age}</td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
};

export default List;

```

### src/store/features/personSlice.ts:

Redux Toolkit のスライスを定義します。このファイルには、スライスの名前、初期状態、アクションクリエーター、およびリデューサーが含まれます。

```tsx:personSlice.tsx
import { createSlice, PayloadAction } from '@reduxjs/toolkit';

export interface Person {
  id: number;
  name: string;
  age: number;
}

interface PersonState {
  persons: Person[];
}

const initialState: PersonState = {
  persons: [],
};

export const PersonSlice = createSlice({
  name: 'person',
  initialState,
  reducers: {
    addPerson: (
      state,
      action: PayloadAction<{ name: string; age: number }>
    ) => {
      state.persons.push({
        id: state.persons.length,
        name: action.payload.name,
        age: action.payload.age,
      });
    },
  },
});
export default PersonSlice.reducer;
export const { addPerson } = PersonSlice.actions;

```

### src/store/store.ts:

Redux ストアを設定し、ルートリデューサーを作成します。useDispatch, useSelector に、適切な型を抽出して使用します。

```tsx:store.tsx
import { configureStore } from '@reduxjs/toolkit';
import { PersonSlice } from './features/personSlice';
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';

export const store = configureStore({
  reducer: {
    person: PersonSlice.reducer,
  },
});
//useDispatch は typeof store.dispatch の型を使用しています。
// これにより、Reduxストアの dispatch 関数の型が正確に推論され、
// アクションをディスパッチする際に適切な型チェックが行われます。
// これにより、タイポやアクションの誤用を防ぎ、コードの安全性を確保します。
export const useAddDispatch: () => typeof store.dispatch = useDispatch;
//useSelector は TypedUseSelectorHook<ReturnType<typeof store.getState>> の
// 型を使用しています。これにより、Reduxストアの状態に関する型情報が提供され、
// コンポーネント内で状態を選択する際に型安全性が確保されます。
// 正確な型情報が提供されることで、状態の型が一致しない場合に発生するエラーを回避できます。
export const useAppSelector: TypedUseSelectorHook<
  ReturnType<typeof store.getState>
> = useSelector;

```

### src/App.tsx:

アプリケーションのエントリーポイント。Add コンポーネントと List コンポーネントを含み、Redux ストアをプロバイダーとして提供します。

```tsx:App.tsx
import './App.css';
import List from './components/List';
import Add from './components/Add';

function App() {
  return (
    <div className="App">
      <Add />
      <List />
    </div>
  );
}

export default App;

```

### src/index.tsx:

アプリケーションをレンダリングするエントリーポイント。

```tsx:index.tsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import { Provider } from 'react-redux/es/exports';
import { store } from './store/store'; // Reduxストアをインポート

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

```
# アプリケーションのデモ

デモの機能は以下の通りです。
1:アプリケーションは一覧表示と追加フォームから構成されています。
2:初期状態では、一覧は空です。
3:ユーザーは「Name」入力フィールドと「Age」入力フィールドに値を入力し、「追加」ボタンをクリックして新しい名前と年齢を追加できます。
4：新しい名前と年齢は一覧に追加され、リスト内で表示されます。

このデモの主要なポイントは、Redux Toolkit を使用してアプリケーションの状態を管理することです。

今回は単純な要素だけで構成されていますが、異なる要素を扱う場合は、「personSlice.ts」のように個別のファイルを作成して管理することで、開発が大規模になっても扱いやすくなります。

# 結論

Redux と TypeScript の組み合わせにより、アプリケーションの開発効率が向上し、ラインタイムエラーや予測できないバグのリスクが低減します。また、コードの可読性と保守性も向上し、大規模なアプリケーションの開発に特に適しています。