Next.js(AppRouter)でRedux Toolkitの導入

Next.jsのApp Routerでの開発で、グローバルステートの管理にRedux Toolkitを導入しようとしましたが、従来のやり方とは少し違ったので、[公式ドキュメント](https://redux.js.org/usage/nextjs)を見ながら導入したので、まとめたいと思います。

### 1. `lib/store.ts`を追加
公式ドキュメント通り追加します。
libディレクトリに配置するのが推奨されていますが、命名は任意のもので良いそうです
```React
import { configureStore } from '@reduxjs/toolkit';
import contersSlice from './reducers/countersSlice';

export const makeStore = () => {
  return configureStore({
    reducer: {
        // ここに使用するSliceを追加
        counters: contersSlice,
    }
  })
}

export type AppStore = ReturnType<typeof makeStore>
export type RootState = ReturnType<AppStore['getState']>
export type AppDispatch = AppStore['dispatch']
```

### 2. `lib/hooks.ts`を追加
こちらも公式ドキュメント通り追加します。
```React
import { useDispatch, useSelector, useStore } from 'react-redux'
import type { AppDispatch, AppStore, RootState } from './store'

export const useAppDispatch = useDispatch.withTypes<AppDispatch>()
export const useAppSelector = useSelector.withTypes<RootState>()
export const useAppStore = useStore.withTypes<AppStore>()
```

### 3. `app/StoreProvider.tsx`を追加
こちらも公式ドキュメント通り追加。
use clientを宣言してClient Componentにします。
```React
'use client'
import { useRef } from 'react'
import { Provider } from 'react-redux'
import { makeStore, AppStore } from '../lib/store'

export default function StoreProvider({
  children
}: {
  children: React.ReactNode
}) {
  const storeRef = useRef<AppStore>()
  if (!storeRef.current) {
    storeRef.current = makeStore()
  }

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

### 4. `app/layout.tsx` を修正
ルートレイアウトで<StoreProvider>を使ってラップします
こうすることで全ルートでstoreを使用することができます
```React
import StoreProvider from './StoreProvider';

// 省略
<StoreProvider>{children}</StoreProvider>
```
今までは直接ここで`<Provider store={store}>`みたいな感じで書いていましたが、
AppRouterはServerComponentのため、エラーが出てしまうみたいです。

### 5. Sliceを追加
数字を増やしたり減らしたりするactionを追加しました。
```React
import { createSlice } from "@reduxjs/toolkit";

export interface CounterType {
  value: number;
}

const initialState: CounterType = {
  value: 0,
};

const countersSlice = createSlice({
  name: "counters",
  initialState,
  reducers: {
    increment: (state) => {
      state.value += 1;
    },
    decrement: (state) => {
      state.value -= 1;
    },
  },
});

export const { increment, decrement } = countersSlice.actions;
export const countersReducer = countersSlice.reducer;
```

### 6. 使ってみる

使用したいコンポーネントで下記のように呼び出したり、値を更新したりできます。
Client Componentでしか使用できないので注意。

```React
'use client';
import React from 'react';
import { useAppSelector, useAppDispatch } from '@/hooks/storeHook';
import { increment, decrement } from '@/states/reducers/countersSlice';

const ExamplePage: React.FC = () => {
  // stateの後のcountersは、lib/store.tsのreducerで設定したkeyです
  const { value } = useAppSelector((state) => state.counters);
  const dispatch = useAppDispatch();

  return (
    <div>
        <button onClick={() => { dispatch(increment()); }}>+1</button>
        <button onClick={() => { dispatch(decrement()); }}>+1</button>
    </div>
  );
};

export default ExamplePage;
```

## 懸念していた点
`<StoreProvider>`はClient Componentなので、
それでラップすると下層は全てClient Componentになるのではないかと心配していましたが、
試しに下層コンポーネントでフックを使おうとするとエラーが出たり
`console.log`を書いてみてもブラウザのコンソールには出力されませんでしたので、
問題なく下層はデフォルトのServerComponentで動作しているようでした。


## 詰まったところ
ページ遷移しても値が保持されているかを調べたく
```HTML
<a href="/b">Bページへ</a>
```
として移動したところ、値が保持されていなかったので
上手く動作しない！と困惑しましたが、`<Link>`を使えば解決しました。
初歩すぎて恥ずかしい、、

```React
import Link from 'next/link';

// 省略
<Link href="/b">Bページへ</Link>
```


## 参考記事
https://zako-lab929.hatenablog.com/entry/20240308/1709899974