React・Reduxを使った一番シンプルなリポジトリ【入門】

# この記事では..
- `npx create-react-app hoge --template typescript`で生成されるReact Typescriptのテンプレートリポジトリを更に簡単にしたリポジトリを作成<br><br>
- その過程でReactのレンダリングについて説明<br><br>
- Reduxについても上と同じテンプレートを少しだけ修正したシンプルなリポジトリを作成<br><br>
- その過程でReduxを使わない場合の実装と比較を行い、Reduxが疎結合を実現する様子を確認

# 参考

・https://qiita.com/FarStep131/items/ad834facc57a443a9dc3

こちらが大変分かりやすい。

# React＋Typescript 環境構築
Homebrewを使ってNode.jsをインストール。手順は[こちら](https://nodejs.org/en/download/package-manager)の通り
```
brew install node@20
```
完了すると、`echo 'export path="/opt/homebrew/opt/node@20/bin:$PATH"' >> ~/.zshrc`の様な形でパスを通すよう表示がされるので、指示通りにパスを通す。

TypeScriptも使える様にしておく。
```
npm install -g typescript 
```

その後、以下のコマンドでReactテンプレートリポジトリを落としてくる。
```
npx create-react-app hoge --template typescript
```

# テンプレートのファイル構成
Typescript＋Reactテンプレートプロジェクトの構成は以下の様になっている。
```
hoge/
├── src/
│   ├── App.css
│   ├── App.test.tsx
│   ├── App.tsx
│   ├── index.css
│   ├── index.tsx
│   ├── logo.svg
│   ├── react-app-env.d.ts
│   ├── reportWebVitals.ts
│   └── setupTests.ts
├── public
│   ├── favicon.ico
│   ├── index.html
│   ├── logo192.png
│   ├── logo512.png
│   ├── manifest.json
│   └── robots.txt    
├── node_modules
│   └── 略
└── tsconfig.json
└── package-lock.json
└── package.json
└── README.md
```
この初期状態のまま`hoge/`直下で`npm start`コマンドを実行すると、以下の様な画面になる。
![スクリーンショット 2024-10-13 16.32.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680028/f85952e5-b8d6-ecc4-31be-04a945ca6382.png)

# テンプレートの中身
## エントリポイントは？
`npm start`を実行した時、最初に`src/index.tsx`が呼び出される。テンプレートの中の`package.json`ファイルにて、以下のように、`npm start`が実行されると`react-scripts start`が実行されている。この`react-scripts`で内部的に`webpack`や`Babel`といったライブラリを使用しており、そこで`src/index.tsx`をentryとして設定している。
```package.json
...
"scripts": {
  "start": "react-scripts start"
  ...
}
```
ちなみに、直接"react-scripts start"をターミナル上で以下の様に実行することも可能である。
```
./node_modules/.bin/react-scripts start
```

## エントリポイント（`src/index.tsx`）の中身は？
エントリポイントである`src/index.tsx`ファイルは以下の様に記述されている。
```src/index.tsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';

const root = ReactDOM.createRoot(
  document.getElementById('root') as HTMLElement
);
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);
```
ここでは、

**`public/index.html`に記述されているDOM要素を識別する`root`というIDに、Reactコンポーネント`App`を追加する"レンダリング"**

が行われている（具体的な説明は後述）。

## もう少し細かい説明
　HTMLやXMLの情報は**DOM**(Document Object Model)によってツリー構造で表現されている。例えば
```hoge.html
<!DOCTYPE html>
<html>
  <head>
    <title>Example Page</title>
  </head>
  <body>
    <div>
      <h1>Hello, World!</h1>
      <p>This is a paragraph.</p>
    </div>
  </body>
</html>
```
のようなHTMLファイルは以下の画像のようにブラウザ上で表示される。

![スクリーンショット 2024-10-13 21.51.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680028/229d0884-b902-bc4f-6925-8422a66e077a.png)

DOMというツリーにおけるノードとは、＜html＞や、＜head＞、＜h1＞の様なHTMLを構成する要素に対応している。つまりDOMでは`html`を１つの頂点とするツリーである。基本的にHTMLでは`head`ノードと`body`ノード１つずつから構成される。

　`head`ではページのタイトルを指定してブラウザのタブに表示させるテキスト（今回だと"Example Page"）を設定できたり、HTMLページの見た目を整えるCSSやタブに表示させるアイコン（ファビコン）を指定したりできる。`body`ではブラウザの画面上に表示させるコンテンツを指定する。今回だと、サイズの違う"Hello, World!"と"This is a paragraph."が表示されている。

　ここで**レンダリング**とは、このHTML上の特定の要素にReactコンポーネントを追加する作業を意味している。そもそもなぜReactコンポーネントを追加するかというと、
 - ブラウザに対するユーザの入力などによる、状態やプロパティの変化に応じて動的にUIを更新できる
 - よく使うUIパーツを簡単に使い回せる
 - 直接DOM操作をするよりも簡単にUIの変更を管理できる

などの利点があるからである。これを踏まえ、テンプレートのエントリポイントのコードに立ち返る。`src/index.tsx`の中で、
```src/index.tsx
...
const root = ReactDOM.createRoot(
  document.getElementById('root') as HTMLElement
);
...
```
なる操作が行われていた。ここで`document`は特にファイル内で明示的に定義がされていなくても使えるグローバルオブジェクトで、現在表示されているHTMLドキュメント全体（ここではテンプレートの`public/index.html`）を表している。`npm start`で実行されている`react-scripts`は内部的に`Webpack Dev Server`を起動しており、このサーバが`http://localhost:3000`にアクセスしたときに`public/index.html`を提供するよう設定してある。
　この操作は、`public/index.html`というdocumentの中で"root"というIDが付いたDOM要素を取得しているのである。ちなみにテンプレートの`public/index.html`の中では、
```public/index.html
<!DOCTYPE html>
<html lang="en">
  <head>
  ...略...
  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
    ...略...
  </body>
</html>
```
の様になっており、"root"というIDが付いたpublic/index.htmlのDOM要素`<div>`に、レンダリングによってReactコンポーネントであるAppが以下の様に追加される。
```src/index.tsx
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);
```
ここでReact.StrictModeは開発過程でのみ使用される、潜在的な問題を見つけやすくするためのラッパーであるため今は無視する。ここでは別ファイルで定義し、`import App from './App';`でインポートしているAppというコンポーネントを追加しているが、ここで何をしているかは一旦考えず、テンプレートよりも更に簡単な静的サイトを作ってみる。

## テンプレートより更に簡単な例を作る
ここでは、さっき"Hello, World!"と"This is a paragraph"を表示したHTML(`hoge.html`)と同じものを、Reactコンポーネントを追加するレンダリングを通じて実装する。そのためにはテンプレートの中でsrc/App.tsxの中身を以下に置き換えるだけでいい。

```src/App.tsx
const App = () => {
  return (
    <div>
      <h1>Hello, World!</h1>
      <p>This is a paragraph.</p>
    </div>
  );
};
export default App;
```
ちなみにDOM要素のIDの名前は好きに決められるので、`public/index.html`と同期さえ取れていれば、別に"root"じゃなくても良い。結果として`hoge.html`と同じ、以下のような出力画面になる（全然Reactを使う意味のない静的サイトが生成される）

![スクリーンショット 2024-10-13 23.10.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680028/0413a714-1088-64e5-6acc-7e139050a1f8.png)


# Reduxも使ってみたい

## 環境構築
テンプレートにはredux, react-reduxは入っていないので、これらをインストールする。（この作業はテンプレートを生成する度に以下の様に手動で行わなければならない点に注意）
```
npm install redux react-redux
```

## 概要
　Reduxはアプリケーションの状態を一元管理してくれるツールである。あるデータ(これを状態と呼ぶ)を**Store**に登録することで、アプリを構成するどのコンポーネントからでも同じ様に登録データにアクセスすることが可能となる。
　ストアに登録する実体は**Reducer**にある。Reducerは、状態とアクションの二つを引数にとって、「あるアクションが来たら状態をどう変更するか」を規定する。あるアクションが起きた時にそれをReduxに認識させることをdispatchと呼ぶ。流れとしては以下の様な形になる。

 ```
 Action -> Dispatch -> ReducerがStateを更新 -> UI上で反映
 ```
具体的な実装を見ないと分からないので、例を見ていく。

## 簡単な例 （Reduxを使わない場合..)
　以下では、次の画像のような"カウントを増やす"ボタンを押すと数字が増える簡単なカウンタを実装する。最初は敢えてReduxは使わずに実装してみる。
 
![スクリーンショット 2024-10-14 14.57.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680028/ef526e8f-7b7d-ca99-f135-aa648e7c3e4c.png)

この実装は、テンプレート初期状態から`src/App.tsx`を以下に置き換えるだけで良い。
```src/App.tsx
import React, { useState } from 'react';

const CounterDisplay: React.FC<{ count: number }> = ({ count }) => {
  return <h1>現在のカウント: {count}</h1>;
};

const CounterControl: React.FC<{ increment: () => void }> = ({ increment }) => { //const CounterControl = ({ increment }) => {
  return <button onClick={increment}>カウントを増やす</button>;
};

const App: React.FC = () => {
  const [count, setCount] = useState(0);

  const increment = () => {
    setCount(count + 1);
  };

  return (
    <div>
      <CounterDisplay count={count} />
      <CounterControl increment={increment} />
    </div>
  );
};

export default App;
```
ここで`const [hoge, fuge] = useState(XX)`は、Reactの状態管理のためのフックである。状態`hoge`に関数`fuge`を作用させることで、状態の更新をReactに認識させ、UI上で反映させる**再レンダリング**が行われる。ここでは、状態として変数`count`が定義されており、それを関数`increment`が更新する。`src/App.tsx`内での役割は以下の画像のように理解できる。
![スクリーンショット 2024-10-14 15.42.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680028/3b945c41-0217-4b6d-506d-d9bba366a1dc.png)

あくまでApp内で`CounterDisplay`と`CounterControl`の紐付けは行われており、その様子は以下の様にまとめられる。

![スクリーンショット 2024-10-14 15.45.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680028/d32778c3-80bc-a4d8-d845-d15c2458ebd0.png)

このように、これほど簡単なアプリでも、コンポーネント間で情報の受け渡しが発生していることがわかる。`CounterDisplay`は`count`を、`CounterControl`は`increment`をAppから引数として渡されている。この例なら全然問題ない気もするが、もっと規模の大きいアプリをReactで作る際にはコンポーネント間での情報の受け渡しは密結合となり、管理が煩雑になってしまう。

## 簡単な例 （Reduxを使った場合)
上と同じことをReduxを使って実装する。そのためにまずテンプレートリポジトリの`src/App.tsx`を編集し、さらに`src/redux/`というディレクトリを作成して3つの新しいファイルを追加する必要がある。作成後のテンプレートリポジトリの構成は以下の様になる。
```
hoge/
├── src/
│   ├── App.css
│   ├── App.test.tsx
│   ├── App.tsx      <-- Modified!!
│   ├── index.css
│   ├── index.tsx    
│   ├── logo.svg
│   ├── react-app-env.d.ts
│   ├── reportWebVitals.ts
│   ├── setupTests.ts
│   └── redux/       <-- New!!
│        ├── action.ts
│        ├── reducer.ts
│        └── store.ts
├── public/
│   ├── favicon.ico
│   ├── index.html
│   ├── logo192.png
│   ├── logo512.png
│   ├── manifest.json
│   └── robots.txt    
├── node_modules/
│   └── 略
└── tsconfig.json
└── package-lock.json
└── package.json
└── README.md
```

まず、`src/App.tsx`を以下の様に書き換える。

```src/App.tsx
import React from 'react';
import { Provider } from 'react-redux';
import store from './redux/store';
import { useDispatch } from 'react-redux';
import { useSelector } from 'react-redux';
import { increment } from './redux/action';

const CounterDisplay: React.FC = () => {
  const count = useSelector((state: { count: number }) => state.count);
  return <h1>現在のカウント: {count}</h1>;
};

const CounterControl: React.FC = () => {
  const dispatch = useDispatch();
  const handleIncrement = () => {
    dispatch(increment());
  };
  return <button onClick={handleIncrement}>カウントを増やす</button>;
};

const App: React.FC = () => {
  return (
    <Provider store={store}>
      <div>
        <CounterDisplay />
        <CounterControl />
      </div>
    </Provider>
  );
};

export default App;
```

さらに、reduxに関して必要な設定を以下のように３つのファイルで行う。
```src/redux/reducer.ts
const initialState = {
  count: 0,
};

const counterReducer = (state = initialState, action: { type: string }) => {
  switch (action.type) {
    case 'INCREMENT':
      return {
        ...state, count: state.count + 1 };
    default:
      return state;
  }
};

export default counterReducer;
```
```src/redux/action.ts
export const increment = () => {
  return {
    type: 'INCREMENT',  // アクションタイプ
  };
};
```
```src/redux/store.ts
import { createStore } from 'redux';
import counterReducer from './reducer';

const store = createStore(counterReducer);

export default store;
```
以上により、同じ出力が得られることになる。
![スクリーンショット 2024-10-14 14.57.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680028/59d3ced9-6c68-791f-a89b-d03c91a206f6.png)


## 今回、Reduxは何を管理してくれていのるか？
　まず、今回`src/App.tsx`内でProviderを使って`CounterDisplay`と`CounterControl`の２つを囲んでいる。ProviderはReduxのメソッドで、引数としてStoreを設定することで、Providerで囲ったコンポーネントにStoreを共有することができる。
　Storeは今回何をどう定義しているか？まずReducerは、`src/redux/reducer.ts`より、Stateとして"カウント"を管理している。初期値を0とし、actionで定義されている"INCREMENT"がDispatchされた時に、+1を行い、+1されたカウントがReducerの返り値となることがわかる。App側では`CounterControl`メソッドはボタンが押された時にReduxストアのアクションを`Dispatch`により更新し、`CounterDisplay`メソッドはReduxストアのState（現在のReducerの返り値）を`useSelector`により取得している。
　
 　この様なStore登録、参照システムであるReduxを使うことで、Reduxを使わない場合には存在していた、`App` - `CounterControl`間と`App` - `CounterDisplay`間の変数の受け渡しは無くなっていることがわかる（Reduxを使った場合では`CounterControl`も`Counterdisplay`も引数が無くなっている：下図参照）。この様に、Reduxを使うことで、新しくReduxストアに登録する手間は増えるものの、アプリを構成するコンポーネント間での情報を同期させ、それらの間の結合を疎にすることができる。
  
![スクリーンショット 2024-10-14 17.53.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680028/cf1fc274-fa14-a59b-8fc4-04e8b00083db.png)


# まとめ

- レンダリングは、HTMLやXMLを構成するツリー状のDOM要素にREACTコンポーネントを追加することで、動的にReactコンテンツをUIに反映させるための仕組みである<br><br>

- Reduxは、共有したいデータ(State)とActionをStoreに登録しておき、ReducerがDispatchされたActionとStateをもとにStateの更新を行うことでコンポーネント間でデータの授受を行わなくても同期を取り、コンポーネント間の疎結合を実現する仕組みである