next.js + typescript で Redux Toolkit を使う最小サンプル
公式のサンプルに使い方が丁寧に書かれているが、
きれいにファイル分割されていて、ひさしぶりに見ると逆に分かりづらかったので、
必要な部分だけ最小の構成でReduxToolkitを使うサンプルをまとめておく。
注意
プロジェクト作成後、サーバを起動したままで進めて行くと、
Providerタグを追加後、useDispatch() を呼び出すところで、
please ensure the component is wrapped in a <Provider>
のエラーになることがあるが、サーバを起動しなおせば正しく動く(はず)。
next.js プロジェクト作成
npx create-next-app redux_toolkit_sample --ts
pages/index.tsx をシンプルな表示にする
pages/index.tsx はカウントとボタンだけを表示するようにしておく。
import { NextPage } from "next"
const Home: NextPage = () => {
return (
<div>
<p>count</p>
<button>click</button>
</div>
);
};
export default Home;
pages/_app.tsx の編集
_app.tsx に必要な処理を追加していく。
公式サンプルではファイルを分けて作成しているが、ここではファイル数も少なくしたいので、_app.tsx に書けるものはここで書いてしまう。
(このセクションの最後に、最終的なコードがあります。)
※分かりやすさ重視で1ファイルにまとめているので、業務で作る場合は公式のサンプルにあるように、features単位でsliceをまとめていくほうが良い。
reduxで管理するstateの作成
カウント数を管理するCounterState型を作成する。
また、初期値を設定する変数を定義する。
// typeの定義
export type CounterState = {
value: number;
};
// 初期値の定義
const initialState: CounterState = { value: 0 };
sliceの作成
カウントを増加させるincrementアクションを作成する。
import { createSlice } from "@reduxjs/toolkit";
export const counterSlice = createSlice({
// 名前
name: "counter",
// 初期値
initialState,
// reducer
reducers: {
increment(state) {
state.value++;
},
},
});
storeの作成
reducerを指定してstoreを作成
import { configureStore, createSlice } from "@reduxjs/toolkit";
export const store = configureStore({
reducer: {
counter: counterSlice.reducer,
},
});
reduxを使うのでProviderで囲む
子コンポーネントをProviderで囲み、storeを指定しておく。
import { Provider } from "react-redux";
function MyApp({ Component, pageProps }: AppProps) {
return (
<Provider store={store}>
<Component {...pageProps} />
</Provider>
);
}
ここまでのコードをまとめたもの
import React from "react";
import type { AppProps } from "next/app";
import { Provider } from "react-redux";
import { configureStore, createSlice } from "@reduxjs/toolkit";
export type CounterState = {
value: number;
};
const initialState: CounterState = { value: 0 };
export const counterSlice = createSlice({
name: "counter",
initialState,
reducers: {
increment(state) {
state.value++;
},
},
});
export const store = configureStore({
reducer: {
counter: counterSlice.reducer,
},
});
function MyApp({ Component, pageProps }: AppProps) {
return (
<Provider store={store}>
<Component {...pageProps} />
</Provider>
);
}
export default MyApp;
pages/index.tsx の編集
実際に表示するページを作成する。
useDispath
アクションを起動する dispatch 関数を store から取得するため、useDispatch で取得しておく。
import { useDispatch } from "react-redux";
const dispatch = useDispatch();
useSelector
storeからstateを持ってくるために useSelectorフックを使う。
今のところ、store には CounterState しか無いので、
store.getState().counter
としても CounterState は取れるが、
フックを使って取るようにしておく。
import { useSelector } from "react-redux";
const selector = useSelector((state: CounterState) => state);
カウンターを表示する
selectorからCounterStateを参照できるので、ここからカウンターの値を取得して表示する。
return (
<div>
<p>{selector.value}</p>
<button>Click</button>
</div>
);
increment アクションを取得する
sliceのactionsから、必要なアクション increment だけ取得する。
const { increment } = counterSlice.actions;
ボタンをクリックしたら increment アクションを実行する
<button
onClick={() => {
dispatch(increment());
}}
>
Click
</button>
ここまでのコードをまとめたもの
import { useDispatch, useSelector } from "react-redux";
import { counterSlice, CounterState, store } from "./_app";
import { NextPage } from "next";
const Home: NextPage = () => {
const dispatch = useDispatch();
const selector = useSelector((state: CounterState) => state);
const { increment } = counterSlice.actions;
return (
<div>
<p>{selector.value}</p>
<button
onClick={() => {
dispatch(increment());
}}
>
Click
</button>
</div>
);
};
export default Home;
storeに複数のstateがある場合
複数のstateを取りまとめるRootStateを作成し、
storeには、それぞれのstateを処理するreducerを登録。
useSelectorで取得したいstateを選択すれば良い。
export type RootState = {
counter: CounterState;
address: AddressState;
};
const counterInitialState: CounterState = {
value: 0,
};
const addressInitialState: AddressState = {
zipCode: "000-0000",
};
export const counterSlice = createSlice({
name: "counter",
initialState: counterInitialState,
reducers: {
increment(state) {
state.value++;
},
},
});
export const addressSlice = createSlice({
name: "address",
initialState: addressInitialState,
reducers: {},
});
export const store = configureStore({
reducer: {
counter: counterSlice.reducer,
address: addressSlice.reducer,
},
});
useSelectorで使うときは、
const counterSelector = useSelector((state: RootState) => state.counter);
const addressSelector = useSelector((state: RootState) => state.address);
Discussion