Reactでfluxの実装をする

## はじめに
表題通り、Reactでfluxのデザインパターンを実装していきます。

## 成果物
題材はカウンターです。+1ボタンを押すとインクリメントされ、リセットボタンを押すとリセットされるよくあるカウンターの実装です。
![スクリーンショット 2023-11-25 13.06.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/599049/65d3daf0-5cbc-94bc-c81e-325bab33c4eb.png)

https://github.com/kouji0705/react_flux

### Hands-on
```shell:ディレクトリ構成
~/develop/react/react_flux$ tree -I node_modules 
.
├── README.md
├── package.json
├── public
│   ├── favicon.ico
│   ├── index.html
│   ├── logo192.png
│   ├── logo512.png
│   ├── manifest.json
│   └── robots.txt
├── src
│   ├── App.tsx
│   ├── Counter.tsx
│   ├── commons
│   │   ├── Store.tsx
│   │   ├── actions.ts
│   │   ├── counterReducer.ts
│   │   ├── reducer.ts
│   │   └── state.ts
│   ├── index.tsx
│   └── logo.svg
├── tsconfig.json
└── yarn.lock

4 directories, 19 files
```

```tsx:index.tsx
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";

const root = ReactDOM.createRoot(
  document.getElementById("root") as HTMLElement
);
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);
```

```tsx:src/App.tsx
import React, { useReducer } from "react";
import { reducer } from "./commons/reducer";
import { initialState } from "./commons/state";
import Store, { StoreContext } from "./commons/Store";
import Counter from "./Counter";

function App() {
  //　state は現在の状態を表す変数、dispatch は状態を変更するための関数
  const [state, dispatch] = useReducer(reducer, initialState);
  // StoreContext は、state と dispatch を保持するコンテキスト
  const context: StoreContext = {
    state,
    dispatch,
  };

  return (
    <Store.Provider value={context}>
      <Counter />
    </Store.Provider>
  );
}

export default App;
```

```tsx:src/Counter.tsx
import React, { useContext } from "react";

import Store from "./commons/Store";
import { COUNTER_INCREMENT, COUNTER_RESET } from "./commons/actions";

export default function Counter() {
  // useContext を使って StoreContext から state と dispatch を取得
  const context = useContext(Store);
  const { state, dispatch } = context;

  const increment = () => dispatch({ type: COUNTER_INCREMENT });

  const reset = () => dispatch({ type: COUNTER_RESET });

  return (
    <div>
      <h1>現在のカウンター値：{state.counter.count}</h1>
      <button onClick={increment}>+1</button>
      <button onClick={reset}>リセット</button>
    </div>
  );
}

```

```tsx:src/commons/actions.ts
export const COUNTER_INCREMENT = "INCREMENT";
export const COUNTER_RESET = "RESET";

type CounterActionTypes = typeof COUNTER_INCREMENT | typeof COUNTER_RESET;

type ActionTypes = CounterActionTypes;

export type Action = {
  type: ActionTypes;
};
```

```tsx:src/commons/counterReducer.ts
import type { Reducer } from "react";
import { COUNTER_INCREMENT, type Action, COUNTER_RESET } from "./actions";
import type { GlobalState } from "./state";

import { initialState } from "./state";

export const counterReducer: Reducer<GlobalState["counter"], Action> = (
  state,
  action
) => {
  switch (action.type) {
    case COUNTER_INCREMENT: {
      const count = state.count + 1;
      return {
        ...state,
        count,
      };
    }

    case COUNTER_RESET: {
      return { ...initialState.counter };
    }

    default:
      return state;
  }
};

```

```tsx:src/commons/reducer.ts
import type { Reducer } from "react";
import type { Action } from "./actions";
import type { GlobalState } from "./state";

import { counterReducer } from "./counterReducer";

export const reducer: Reducer<GlobalState, Action> = (state, action) => {
  return {
    action,
    counter: counterReducer(state.counter, action),
  };
};

```

```tsx:src/commons/state.ts
import type { Action } from "./actions";

export type GlobalState = {
  action: Action | null;
  counter: CounterState;
};

type CounterState = {
  count: number;
};

export const initialState: GlobalState = {
  action: null,
  counter: {
    count: 0,
  },
};

```

```tsx:src/commons/Store.tsx
import type { GlobalState } from "./state";
import type { Action } from "./actions";

import React from "react";

export type StoreContext = {
  state: GlobalState;
  dispatch: (action: Action) => void;
};

const Store = React.createContext({} as StoreContext);

export default Store;

```