AppRouter & Redux サンプルコードで確認しよう(npx create-next-app --example with-redux)

``npx create-next-app --example with-redux``で生成されるサンプルコードを用いNextjs14とReduxについて学んでみた。

## 読んで欲しい人
- Redux を使ってみたい
- Nextjsを使った事があるがAppRouterの仕組みが分かっていない
- Reduxを最新のNextjsと一緒に使ってみたい


# Nextjs とは
Next.jsは、Reactアプリケーションをより簡単に、より高速に構築するためのフレームワーク

### 3つの特徴
- 複数のレンダリング方法を持っている(サーバーサイド・クライアントサイド どちらも可能)
- フォルダ名でそのままルーティングが行える (PageRouter・AppRouter)
- API Routesなどバックエンド処理の作成が可能!


### AppRouter
**Next.js の v13.4からの新機能**
- これまでのNext.jsはpagesディレクトリに作成したファイル名やディレクトリ名がそのまま URL のパスになっていた
- これからはappディレクトリ配下にディレクトリを作りファイル名はpage.jsxで書くことでURLのパスになる

ファイルの名前に意味を持つものも多くあるよ👇👇👇

https://nextjs.org/docs/getting-started/project-structure

# Redux とは
ReactJSが扱うUIのstate(状態)を管理をするためのフレームワーク
Reduxはstateを管理するためのライブラリーなので、React以外にもAngularJSやjQueryなどと併せて使用することもできるが、Reactと使用するのが一番相性がいい

### 相関図で理解を深める
![redux-basics-animation-1b.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3118679/6ac49ce9-1fce-0f8d-f016-ad3baf64905c.gif)

まずUIを見てみる。UIに対して$10を入金する操作をした場合を考えよう
- UIで行った変更はClickEventとして**EventHandler**に届く
- 届いたEvent(type:入金or出金,payload:金額)を変更&保存するために**Store**に送る必要がある
- その処理の流れを**ActionをStoreへDispatch（送信）する**という
- Actionをもとに**Reducer**と呼ばれる関数がStateを更新
- 最後に更新後のStateがUIに届く

**重要な用語**
- **Dispatch**　Storeにイベントが発生したことを知らせる役割
- **Action**　アプリケーション上で起こったイベント（ユーザーがボタンを押すなど）
- **Store**　Reduxアプリケーションの状態を保持するオブジェクト
- **Reducer**　StateとActionオブジェクトを受け取り、新しいStateを返す関数

今回のサンプルではreduxが推奨しているredux-toolkitを使用している

# サンプルコードを見てみよう
``npm run dev``を実行し http://localhost:3000/ を開くと以下のページが出現
#### 主な機能
- **+/-ボタン** : 数字を1ずつ増やす、または減らす
-  **AddAmount** : 左に表示された数値分値を変更
- **AddAsync** : 非同期で、左に表示された数値分値を変更
- **Add If Odd** : 値が奇数の場合のみ、左に表示された数値分値を変更

![redux-sample.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3118679/7af118a6-f52c-5b23-cd67-690311290d8d.gif)

#### Quotesを押すと別ページへ
指定した数の**格言**が表示される
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3118679/a3f16301-601d-f78a-1681-3b34eae4a624.png)


### ディレクトリ構成
重要なポイントを示す

#### appフォルダ
- app直下の``page.tsx``,``layout.tsx``は特別
- ``StoreProvider.tsx``
- apiフォルダは「」を示す

#### libフォルダ
Reduxに関連するファイルが多い
- ``createAppSlice.ts``
- ``hooks.ts``
- ``store.ts``

```yml
実際の構成
.
├── README.md
├── app
│   ├── StoreProvider.tsx
│   ├── api
│   │   └── counter
│   ├── components
│   │   ├── Nav.tsx
│   │   ├── counter
│   │   └── quotes
│   ├── icon.ico
│   ├── layout.tsx
│   ├── page.tsx
│   ├── quotes
│   │   └── page.tsx
│   ├── styles
│   │   ├── globals.css
│   │   └── layout.module.css
│   └── verify
│       └── page.tsx
├── lib
│   ├── createAppSlice.ts
│   ├── features
│   │   ├── counter
│   │   └── quotes
│   ├── hooks.ts
│   └── store.ts
├── next-env.d.ts
├── next.config.mjs
├── package-lock.json
├── package.json
├── public
│   └── logo.svg
└── tsconfig.json
```

# ここからが本番　コードを読み解く

### `app/layout.tsx`

<details><summary>code</summary>

```tsx
import Image from "next/image";
import type { ReactNode } from "react";
import { StoreProvider } from "./StoreProvider";
import { Nav } from "./components/Nav";

import "./styles/globals.css";
import styles from "./styles/layout.module.css";

interface Props {
  readonly children: ReactNode;
}

export default function RootLayout({ children }: Props) {
  return (
    <StoreProvider>
      <html lang="en">
        <body>
          <section className={styles.container}>
            <Nav />

            <header className={styles.header}>
              <Image
                src="/logo.svg"
                className={styles.logo}
                alt="logo"
                width={100}
                height={100}
              />
            </header>

            <main className={styles.main}>{children}</main>

            <footer className={styles.footer}>
              <span>Learn </span>
              <a
                className={styles.link}
                href="https://reactjs.org"
                target="_blank"
                rel="noopener noreferrer"
              >
                React
              </a>
              <span>, </span>
              <a
                className={styles.link}
                href="https://redux.js.org"
                target="_blank"
                rel="noopener noreferrer"
              >
                Redux
              </a>
              <span>, </span>
              <a
                className={styles.link}
                href="https://redux-toolkit.js.org"
                target="_blank"
                rel="noopener noreferrer"
              >
                Redux Toolkit
              </a>
              <span>, </span>
              <a
                className={styles.link}
                href="https://react-redux.js.org"
                target="_blank"
                rel="noopener noreferrer"
              >
                React Redux
              </a>
              ,<span> and </span>
              <a
                className={styles.link}
                href="https://reselect.js.org"
                target="_blank"
                rel="noopener noreferrer"
              >
                Reselect
              </a>
            </footer>
          </section>
        </body>
      </html>
    </StoreProvider>
  );
}
```

</details>

<p>アプリケーションのルートレイアウトを定義しているファイル。</p>
<p>StoreProviderで全体を囲むことによってReduxストアをアプリケーション全体に提供している。</p>
<p>また、ナビゲーション、ヘッダー、メインコンテンツ、フッターなどのレイアウト構造が定義されている。</p>

### `app/page.tsx`

<details><summary>code</summary>

```tsx
import type { Metadata } from "next";
import { Counter } from "./components/counter/Counter";

export default function IndexPage() {
  return <Counter />;
}

export const metadata: Metadata = {
  title: "Redux Toolkit",
};
```
</details>


<p>アプリケーションのルートページを定義しているファイル。</p>
<p>Counterコンポーネントをレンダリングしている。</p>
<p>metadataオブジェクトでページのタイトルを設定。</p>

### `app/StoreProvider.tsx`

<details><summary>code</summary>

```tsx
"use client";
import type { AppStore } from "@/lib/store";
import { makeStore } from "@/lib/store";
import { setupListeners } from "@reduxjs/toolkit/query";
import type { ReactNode } from "react";
import { useEffect, useRef } from "react";
import { Provider } from "react-redux";

interface Props {
  readonly children: ReactNode;
}

export const StoreProvider = ({ children }: Props) => {
  const storeRef = useRef<AppStore | null>(null);

  if (!storeRef.current) {
    // Create the store instance the first time this renders
    storeRef.current = makeStore();
  }

  useEffect(() => {
    if (storeRef.current != null) {
      // configure listeners using the provided defaults
      // optional, but required for `refetchOnFocus`/`refetchOnReconnect` behaviors
      const unsubscribe = setupListeners(storeRef.current.dispatch);
      return unsubscribe;
    }
  }, []);

  return <Provider store={storeRef.current}>{children}</Provider>;
};

```

</details>

<p>Reduxストアをアプリケーション全体に提供するためのプロバイダーコンポーネントを定義しているファイル。</p>
<p>プロバイダーはクライアント側の処理になるため<font><code>"use client"</code></font>を使用。</p>
<p>makeStore関数を使用してストアを作成し、storeRefに保持。</p>
<p><font color="red"><b>setupListeners</b></font>を使用して、リスナーを設定。</p>
<p>→setupListenersはアプリケーションの初期化時に一度だけ呼び出される。</p>
<p>→setupListenersを呼び出すことで、<b>refetchOnFocus</b>や<b>refetchOnReconnect</b>などの機能が有効になる。</p>

- **refetchOnFocus**:ウィンドウがフォーカスを取り戻したときに、サブスクライブされているすべてのクエリを自動的に再フェッチする機能→ユーザーが別のタブやアプリケーションから戻ってきたときに、最新のデータを取得することが可能になる
- **refetchOnReconnect**:ネットワーク接続が復活したときに、サブスクライブされているすべてのクエリを自動的に再フェッチする機能→ネットワーク接続が切断され再接続されたときに、最新のデータを取得することが可能

### `lib/store.ts`

<details><summary>code</summary>

```tsx
import type { Action, ThunkAction } from "@reduxjs/toolkit";
import { combineSlices, configureStore } from "@reduxjs/toolkit";
import { counterSlice } from "./features/counter/counterSlice";
import { quotesApiSlice } from "./features/quotes/quotesApiSlice";

// `combineSlices` automatically combines the reducers using
// their `reducerPath`s, therefore we no longer need to call `combineReducers`.
const rootReducer = combineSlices(counterSlice, quotesApiSlice);
// Infer the `RootState` type from the root reducer
export type RootState = ReturnType<typeof rootReducer>;

// `makeStore` encapsulates the store configuration to allow
// creating unique store instances, which is particularly important for
// server-side rendering (SSR) scenarios. In SSR, separate store instances
// are needed for each request to prevent cross-request state pollution.
export const makeStore = () => {
  return configureStore({
    reducer: rootReducer,
    // Adding the api middleware enables caching, invalidation, polling,
    // and other useful features of `rtk-query`.
    middleware: (getDefaultMiddleware) => {
      return getDefaultMiddleware().concat(quotesApiSlice.middleware);
    },
  });
};

// Infer the return type of `makeStore`
export type AppStore = ReturnType<typeof makeStore>;
// Infer the `AppDispatch` type from the store itself
export type AppDispatch = AppStore["dispatch"];
export type AppThunk<ThunkReturnType = void> = ThunkAction<
  ThunkReturnType,
  RootState,
  unknown,
  Action
>;

```

</details>

<p>Reduxストアの設定を行っているファイル。</p>
<p><b>combineSlices</b>を使用して、counterSliceとquotesApiSliceのReducerを自動的に結合。</p>
<p><b>makeStore</b>関数でストアを作成し、ミドルウェアを設定。
<p>quotesApiSliceのミドルウェアを追加。</p>
<p>RootState、AppStore、AppDispatch、AppThunkの型を定義</p>

:::note info
ここで出てくる重要な要素　**Sliceとは？？**
:::

関連するstateとactionを1つのファイルにまとめて管理できるもの...
**つまりstateの初期値、stateを更新するためのreducer、actionを一括で定義できるもの**
Redux ToolkitのcreateSlice関数を使うことで、actionのtype定数やaction creatorを自動で生成してくれる！

### `createAppSlice.ts`

<details><summary>code</summary>

```tsx
import { asyncThunkCreator, buildCreateSlice } from "@reduxjs/toolkit";

// `buildCreateSlice` allows us to create a slice with async thunks.
export const createAppSlice = buildCreateSlice({
  creators: { asyncThunk: asyncThunkCreator },
});

```

</details>

**buildCreateSlice**を使用して、カスタマイズしたSliceを利用できるようにする
今回は非同期処理を含むアクションをディスパッチするためのThunkを用いたSliceを作成する
作成したSliceの名前は**CreaateSlice**

:::note info
*補足:Thunkは、評価を遅延させるために式をラップする関数のこと
Redux Thunkは、このThunkの概念をReduxで利用できるようにするミドルウェア

使用例）APIリクエストを送信し、そのレスポンスに基づいてアクションをディスパッチするような場合に便利

:::

### `lib/features/counter/counterSlice.ts`

<details><summary>code</summary>

```tsx
import { createAppSlice } from "@/lib/createAppSlice";
import type { AppThunk } from "@/lib/store";
import type { PayloadAction } from "@reduxjs/toolkit";
import { fetchCount } from "./counterAPI";

export interface CounterSliceState {
  value: number;
  status: "idle" | "loading" | "failed";
}

const initialState: CounterSliceState = {
  value: 0,
  status: "idle",
};

// If you are not using async thunks you can use the standalone `createSlice`.
export const counterSlice = createAppSlice({
  name: "counter",
  // `createSlice` will infer the state type from the `initialState` argument
  initialState,
  // The `reducers` field lets us define reducers and generate associated actions
  reducers: (create) => ({
    increment: create.reducer((state) => {
      // Redux Toolkit allows us to write "mutating" logic in reducers. It
      // doesn't actually mutate the state because it uses the Immer library,
      // which detects changes to a "draft state" and produces a brand new
      // immutable state based off those changes
      state.value += 1;
    }),
    decrement: create.reducer((state) => {
      state.value -= 1;
    }),
    // Use the `PayloadAction` type to declare the contents of `action.payload`
    incrementByAmount: create.reducer(
      (state, action: PayloadAction<number>) => {
        state.value += action.payload;
      },
    ),
    // The function below is called a thunk and allows us to perform async logic. It
    // can be dispatched like a regular action: `dispatch(incrementAsync(10))`. This
    // will call the thunk with the `dispatch` function as the first argument. Async
    // code can then be executed and other actions can be dispatched. Thunks are
    // typically used to make async requests.
    incrementAsync: create.asyncThunk(
      async (amount: number) => {
        const response = await fetchCount(amount);
        // The value we return becomes the `fulfilled` action payload
        return response.data;
      },
      {
        pending: (state) => {
          state.status = "loading";
        },
        fulfilled: (state, action) => {
          state.status = "idle";
          state.value += action.payload;
        },
        rejected: (state) => {
          state.status = "failed";
        },
      },
    ),
  }),
  // You can define your selectors here. These selectors receive the slice
  // state as their first argument.
  selectors: {
    selectCount: (counter) => counter.value,
    selectStatus: (counter) => counter.status,
  },
});

// Action creators are generated for each case reducer function.
export const { decrement, increment, incrementByAmount, incrementAsync } =
  counterSlice.actions;

// Selectors returned by `slice.selectors` take the root state as their first argument.
export const { selectCount, selectStatus } = counterSlice.selectors;

// We can also write thunks by hand, which may contain both sync and async logic.
// Here's an example of conditionally dispatching actions based on current state.
export const incrementIfOdd =
  (amount: number): AppThunk =>
  (dispatch, getState) => {
    const currentValue = selectCount(getState());

    if (currentValue % 2 === 1 || currentValue % 2 === -1) {
      dispatch(incrementByAmount(amount));
    }
  };

```

</details>

``createAppSlice.ts``で作成した**createSlice**を用いて**counter**に関連するReduxスライスを定義
まず、スライスのパラメータにはvalue と status を持つようにインターフェースと初期値を定義
アクションとそれに対応するステート更新ロジックを定義

**``reducersフィールド``** : スライスのリデューサーを定義
``increment、decrement、incrementByAmount``は、同期的なリデューサー

:::note info
**PayloadAction**は、Redux Toolkitが提供する型で、actionのペイロード（追加のデータ）の型を定義する
:::

:::note info
**incrementByAmount部分の説明** 
actionがディスパッチされると、指定された数値がpayloadとしてreducerに渡され、その値だけステートのvalueが増加
:::

``incrementAsync``は、非同期のThunk
これは、**fetchCount関数**を使用してAPIリクエストを行い、レスポンスに基づいて
状態を更新（fetchCount関数に関してはcounterAPI.tsを参照）
Thunkは、pending、fulfilled、rejectedの3つのステータスを持ち、それぞれの状態に応じて状態を更新

**``selectors フィールド``** : スライスのセレクター関数を定義
これらのセレクターは、スライスの状態を受け取り、必要な情報を返す

**``counterSlice.actions``**　各リデューサーに対応するアクションクリエイターを生成

**``counterSlice.selectors``** 定義したセレクター関数を取得

``incrementIfOdd``は手動で作成したThunkの例

``selectCount(getState())``で取得した現在の状態に基づいて奇数の場合にのみ、``incrementByAmount``アクションをディスパッチする


### `lib/features/quotes/quotesApiSlice.ts`

:::note warn
**RTK Queryとは？**
Redux Toolkitに含まれるAdd On
データフェッチおよびキャッシュツールで、自分でロジックを書く必要がなくなる
Webアプリケーションの多くは、サーバーからデータをフェッチして、データを表示させます。また、そのデータを更新し、更新したデータをサーバーに送信し、クライアント上のキャッシュされたデータをサーバー上のデータと同期させます。
:::


:::note warn
**createApiとは？**　createSliceとの関係

createApiはcreateSliceの上に構築されているもの
createApiは、APIとのデータのやり取りを管理するためのエンドポイントを定義し、定義されたエンドポイントに対応するReact hooksを自動生成する

https://redux-toolkit.js.org/rtk-query/api/createApi

:::

<details><summary>code</summary>

```tsx
// Need to use the React-specific entry point to import `createApi`
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";

interface Quote {
  id: number;
  quote: string;
  author: string;
}

interface QuotesApiResponse {
  quotes: Quote[];
  total: number;
  skip: number;
  limit: number;
}

// Define a service using a base URL and expected endpoints
export const quotesApiSlice = createApi({
  baseQuery: fetchBaseQuery({ baseUrl: "https://dummyjson.com/quotes" }),
  reducerPath: "quotesApi",
  // Tag types are used for caching and invalidation.
  tagTypes: ["Quotes"],
  endpoints: (build) => ({
    // Supply generics for the return type (in this case `QuotesApiResponse`)
    // and the expected query argument. If there is no argument, use `void`
    // for the argument type instead.
    getQuotes: build.query<QuotesApiResponse, number>({
      query: (limit = 10) => `?limit=${limit}`,
      // `providesTags` determines which 'tag' is attached to the
      // cached data returned by the query.
      providesTags: (result, error, id) => [{ type: "Quotes", id }],
    }),
  }),
});

// Hooks are auto-generated by RTK-Query
// Same as `quotesApiSlice.endpoints.getQuotes.useQuery`
export const { useGetQuotesQuery } = quotesApiSlice;

```

</details>

``createApi``は、APIスライスを作成するための関数

``fetchBaseQuery``は、APIリクエストを送信するためのベースクエリ関数

``Quoteインターフェース`` : APIから返される引用オブジェクトの型を表す

``QuotesApiResponseインターフェース`` : APIレスポンスの型 quotesは引用オブジェクトの配列、totalは全引用数、skipはスキップした引用数、limitは取得する引用数を表す

createApi関数を使用して、quotesApiSliceを作成
この関数内では、ベースクエリ、リデューサーパス、タグタイプ、エンドポイントを指定

``baseQuery`` : fetchBaseQuery関数を使用して、APIのベースURLを指定
``reducerPathオプション`` : 生成されるAPIスライスのreducerが、Reduxのルートステートオブジェクト内のどのパスに配置されるかを指定するもの
つまり``reducerPath: "quotesApi"``と指定することで、このAPIスライスによって管理されるステートは、Reduxのルートステート内の``state.quotesApi``というパスに配置される

``tagTypes`` : キャッシュと無効化に使用するタグタイプを指定している
``getQuotes``エンドポイントが``'Quotes'``タグを提供することを指定

``endpoints`` : APIエンドポイントを定義
ここでは、``getQuotesクエリ``を定義

``getQuotesクエリ``は、limitパラメータを受け取り、APIリクエストのクエリ文字列を生成
戻り値の型はQuotesApiResponse、クエリ引数の型はnumber
``providesTags`` : クエリの結果にタグを付けるために使用
ここでは、"Quotes"タグとidを使用

``useGetQuotesQueryフック``を自動生成している
このフックは、コンポーネント内で使用して、APIからデータを取得することが可能


### `lib/hooks.ts`

<details><summary>code</summary>

```tsx
// This file serves as a central hub for re-exporting pre-typed Redux hooks.
import { useDispatch, useSelector, useStore } from "react-redux";
import type { AppDispatch, AppStore, RootState } from "./store";

// Use throughout your app instead of plain `useDispatch` and `useSelector`
export const useAppDispatch = useDispatch.withTypes<AppDispatch>();
export const useAppSelector = useSelector.withTypes<RootState>();
export const useAppStore = useStore.withTypes<AppStore>();

```

</details>

このファイルは、型付きのReduxフックを再エクスポートするための中心的なハブとして機能している
hooks.tsファイルの主な目的は、TypeScriptを使用する際に型安全性を提供すること
JavaScriptで開発している場合、このファイルは不要
JavaScriptにはTypeScriptのような静的型付けがないため、useDispatch、useSelector、useStoreをそのまま使用できる

``react-redux``から``useDispatch``、``useSelector``、``useStore``をインポート
``./store``から``AppDispatch``、``AppStore``、``RootState``の型をインポート

``useAppDispatch``は、useDispatchをAppDispatch型で型付けしたもの
``useAppSelector``は、useSelectorをRootState型で型付けしたもの
``useAppStore``は、useStoreをAppStore型で型付けしたもの

### `lib/features/counter/counterAPI.ts`

<details><summary>code</summary>

```tsx
// A mock function to mimic making an async request for data
export const fetchCount = async (amount = 1) => {
  const response = await fetch("http://localhost:3000/api/counter", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ amount }),
  });
  const result: { data: number } = await response.json();

  return result;
};
```

</details>

カウンターに関連するAPIリクエストを行うためのモックファイル
fetchCount 関数は、指定された amount の値を使用して ``/api/counter`` エンドポイントにPOSTリクエストを送信し、レスポンスを返す
fetchCount関数は、amountという引数を受け取る（デフォルト値は1）
関数はasyncキーワードを使用して定義されているため、非同期的に実行される

### `app/api/counter/route.ts`

<details><summary>code</summary>

```tsx
import type { NextRequest } from "next/server";
import { NextResponse } from "next/server";

interface Context {
  params: undefined;
}

export async function POST(request: NextRequest, context: Context) {
  const body: { amount: number } = await request.json();
  const { amount = 1 } = body;

  // simulate IO latency
  await new Promise((resolve) => setTimeout(resolve, 500));

  return NextResponse.json({ data: amount });
}
```

</details>

Next.jsのAPIルートを定義しているファイル
NextRequestとNextResponseをインポート
Contextインターフェースを定義し、paramsプロパティを持たせる
POST関数を定義し、NextRequestとContextを引数として受け取る
``request.json()``を使用して、リクエストボディからJSONデータを取得
amountプロパティをリクエストボディから取得し、デフォルト値として1を設定
``setTimeout``を使用して、IO遅延をシミュレートしています。500ミリ秒の遅延を導入
``NextResponse.json()``を使用して、amountの値をJSONレスポンスとして返す

:::note info
**つまり**
``route.ts``ファイルでは、``/api/counter``エンドポイントに対するPOSTリクエストを処理

fetchを使用して``http://localhost:3000/api/counter``にPOSTリクエストを送信し、amountの値を設定

``counterSlice.ts``内で、fetchCount関数を呼び出し、返された値を使用してステートを更新
:::

### NEXT >>>　
#### サンプルコードをもとにしたログイン機能を持った情報管理アプリを作成。