Reduxの基礎として”React初心者でも読めば必ずわかるReactのRedux講座”を公開していますが本文書ではRedux Toolkitの使用方法について説明を行っています。

Reduxは難解だとイメージを持っている人も多いと思うので可能な限りシンプルなコードを利用しているので安心して読み進めることができます。

前半はRedux Toolkitを理解するためにRedux Toolkitを利用しない場合のカウンターの作成します。その後、Redux Toolkitをインストールを行いRedux Toolkitを利用したコードに書き換えていきます。

後半はRedux Toolkitにおける非同期処理についての説明も行っています。Redux ToolkitだけではなくRedux ToolkitにおけるRedux Thunkの利用方法も一緒に理解することができます。

最後にTypeScriptを使った場合でのカウンターのコード記述しています。

Redux Toolkitとは

Reduxは複数のコンポーネントからアクセス可能なデータを一箇所で一元管理するためのライブラリです。Redux Toolkitの利用は必須ではなくReduxのみ利用しても同じアプリケーションを開発することは可能です。ではなぜRedux Toolkitが必要になるのでしょう?

Reduxのみでは以下のような問題点を抱えていると言われています。

  • Redux Storeを構成することが複雑すぎる
  • 大規模のアプリケーションを構築するにはたくさんの追加パッケージをインストールする必要がある
  • Reduxを利用するためには定型文的なコードを大量に記述する必要がある

上記の問題点を解消することを目的に開発されたライブラリがRedux Toolkitです。Reduxの公式ホームページではReduxを利用する際にはRedux Toolkitを利用することを推奨しています。

Redux Toolkitを利用することで定型文的なコード量を減らすことができる上、開発者が陥りがちな問題を避けるためにこれまで蓄積されたベストプラクティスが反映されているためReduxのコードを効率よく記述できるようになっています。

デフォルトでRedux Devtoolsの設定が行われ、非同期処理に利用されるRedux Thunkを追加インストールすることなしに利用できるといったこともRedux Toolkitでは行われています。
fukidashi

Reactプロジェクトの作成

Reduxの説明を行う前に動作確認を行う環境を構築するためにViteを利用してReactプロジェクトの作成を行います。”npm create vite@latext”コマンドを実行するとプロジェクト名、フレームワーク、Variantを聞かれるので”redux-learn”, “React”, “JavaScript”を選択します。プロジェクト名は任意の名前をつけてください。


% npm create vite@latest
> npx
> create-vite

✔ Project name: … redux-toolkit-beginner
✔ Select a framework: › React
✔ Select a variant: › JavaScript

Scaffolding project in /Users/mac/Desktop/redux-toolkit-beginner...

Done. Now run:

  cd redux-toolkit-beginner
  npm install
  npm run dev
本文書ではnpmコマンドを利用していますがyarn, pnpmコマンドを使うことも可能です。
fukidashi

作成したプロジェクトディレクトリに移動して、”npm install”コマンドを実行してJavaScriptライブラリのインストールを行い、npm run devコマンドで開発サーバの起動を行います。


 % cd redux-learn
 % npm install
 % npm run dev

> redux-learn@0.0.0 dev
> vite
  VITE v5.3.4  ready in 724 ms

  ➜  Local:   http://localhost:5173/
  ➜  Network: use --host to expose
  ➜  press h + enter to show help

http://localhost:5173にブラウザからアクセスすると下記の初期画面が表示されます。

Vite+Reactのトップページ
Vite+Reactのトップページ

デフォルトで設定されているスタイルを解除するためにsrcディレクトリのmain.jsxファイルを開いてindex.cssのimportをコメントします。


import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.jsx'
// import './index.css'

ReactDOM.createRoot(document.getElementById('root')).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
)

Reduxを利用しないカウンターの作成

プロジェクトフォルダ作成後App.jsxを開いてReduxを利用しない場合のカウンターのコードを記述します。

useState Hookでcount変数を定義し、ボタンを2つ用意します。片方のボタンをクリックするとcountの値が1増え、もう一方のボタンをクリックするとcountの値が減ります。


import { useState } from 'react';

function App() {
  const [count, setCount] = useState(0);
  return (
    <div>
      <h1>Count: {count}</h1>
      <button onClick={() => setCount(count + 1)}>Up</button>
      <button onClick={() => setCount(count - 1)}>Down</button>
    </div>
  );
}

export default App;

ブラウザを利用してにアクセスすると以下の画面が表示されます。ボタンをクリックしてcountの数が変わることを確認してください。

Reduxを利用しない場合のカウンター
Reduxを利用しない場合のカウンター

Reduxライブラリのインストール

Reduxとredux toolkitパッケージのインストールを行います。


 $ npm install @reduxjs/toolkit react-redux

インストールが完了したら動作確認で利用するライブラリとバージョンをpackage.jsonで確認しておきます。


{
  "name": "redux-toolkit-beginner",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0",
    "preview": "vite preview"
  },
  "dependencies": {
    "@reduxjs/toolkit": "^2.2.6",
    "react": "^18.3.1",
    "react-dom": "^18.3.1",
    "react-redux": "^9.1.2"
  },
  "devDependencies": {
    "@types/react": "^18.3.3",
    "@types/react-dom": "^18.3.0",
    "@vitejs/plugin-react": "^4.3.1",
    "eslint": "^8.57.0",
    "eslint-plugin-react": "^7.34.3",
    "eslint-plugin-react-hooks": "^4.6.2",
    "eslint-plugin-react-refresh": "^0.4.7",
    "vite": "^5.3.4"
  }
}

Redux Toolkitの設定

Storeの作成

Reduxではすべてのコンポーネントからアクセス可能なstoreと呼ばれる場所を作成する必要があります。srcフォルダにreduxフォルダを作成しその中にstore.jsファイルを作成します。redux, store.jsという名前をつけていますがフォルダ名もファイル名も任意の名前をつけることができます。

store.jsファイルの中ではStoreを作成するために記述するコードhttps://redux-toolkit.js.org/tutorials/quick-startを参考に記述しています。


import { configureStore } from '@reduxjs/toolkit';

export const store = configureStore({
  reducer: {},
});

Providerコンポーネントの設定

store.jsファイル作成後、作成したstoreにすべてのコンポーネントからアクセスできるようにmain.jsxファイルを更新します。react-reduxからimportしたProviderでAppコンポーネントを包みます。さらに作成したstoreをimportしてpropsとしてProviderコンポーネントに渡します。


import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App.jsx';
// import './index.css'
import { store } from './redux/store';
import { Provider } from 'react-redux';

ReactDOM.createRoot(document.getElementById('root')).render(
  <React.StrictMode>
    <Provider store={store}>
      <App />
    </Provider>
  </React.StrictMode>
);

Sliceファイルの作成

カウンターの変数count、countの初期値やcountの値を更新する関数をRedux ToolkitではSliceファイルに追加していきます。Sliceファイルは管理したいデータを目的別/機能別に分けます。ここではカウンターの変数countに関する設定をSliceファイルであるcounterSlice.jsファイルにすべて設定します。もしユーザ情報をRedux Toolkitで管理したい場合はuserSlice.jsといった名前の別ファイルを作成しユーザ情報の初期値や更新に利用する関数を記述します。Sliceでデータを目的別にわけることでデータ管理の混乱を避け効率的に管理することができます。Sliceファイルの中で初期値、reducer、Action creatorsを設定することができるためそれぞれの処理を複数のファイルに分けて記述する必要がありません。

reduxフォルダの中にcounterSlice.jsファイルを作成し、Redux ToolkitからcreateSlice関数をimportします。createSliceの引数にはname, initialState, reducersプロパティを持つオブジェクトを指定します。


import { createSlice } from '@reduxjs/toolkit';

export const counterSlice = createSlice({
  name: 名前,
  initialState: {
       初期値
  },
  reducers: {
    関数
  },
});

nameにはSliceを識別するための名前を設定します。initialStateには共有するデータ(state)の初期値を設定し、reducersの中にはstateを更新するための関数を設定します。

下記のコードではsliceの名前をcounter、countの初期値を0に設定し、reducersの中でincrease関数とdecrease関数を定義しています。increase関数では引数にstateが入りcountの値を1増やす処理を行います。decrease関数では引数にstateが入りcountの値を1減らす処理を行います。


import { createSlice } from '@reduxjs/toolkit';

export const counterSlice = createSlice({
  name: 'counter',
  initialState: {
    count: 0,
  },
  reducers: {
    increase: (state) => {
      state.count += 1;
    },
    decrease: (state) => {
      state.count -= 1;
    },
  },
});

reducersの中で設定した関数increase, decreaseはredux Toolkitでは自動で同名のAction creatorsを作成します。Action creatorsを後ほど出てくるdispatchで指定するためexportを行い他のコンポーネントからimportできるようにします。


import { createSlice } from '@reduxjs/toolkit';

export const counterSlice = createSlice({
  name: 'counter',
  initialState: {
    count: 0,
  },
  reducers: {
    increase: (state) => {
      state.count += 1;
    },
    decrease: (state) => {
      state.count -= 1;
    },
  },
});

export const { increase, decrease } = counterSlice.actions;

export default counterSlice.reducer;

StoreへのSliceの追加

作成したcounterSliceはstore.jsのstoreに登録する必要があります。toolkitからimportしたconfigureStore関数の引数に設定するオブジェクトのreducerプロパティに追加します。


import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './counterSlice';

export const store = configureStore({
  reducer: {
    counter: counterReducer,
  },
});
複数のSliceを作成した場合は作成したSliceをimportしてreducerプロパティのオブジェクトに追加することで各Sliceで設定したstateをすべてのコンポーネントからアクセスすることが可能になります。combineReducersを利用しなくてもRedux Toolkitが自動で設定を行なってくれます。
fukidashi

これでRedux Toolkitの設定は完了です。ここからは設定したstateをAppコンポーネントで利用する方法を確認していきます。

useSelectorの設定

useSelector Hookを利用することでcounterSliceで設定したcountの値を取得することができます。stateのドットの直後に指定しているcounterはstore.jsのreducerに設定したオブジェクトのプロパティのcounterに対応します。counterSlice.jsファイルのnameで設定した”counter”ではありません。countの値を取得したい場合はstate.countではなくstate.counter.countであることを注意してください。


import './App.css';
import { useSelector } from 'react-redux';

function App() {
  const count = useSelector((state) => state.counter.count);
  return (
    <div className="App">
      <h1>Count: {count}</h1>
    </div>
  );
}

export default App;

counterSliceに設定したcountを表示させるだけであればuseSelectorを利用するだけで完了です。

useSelectorでcountの値を取得
useSelectorでcountの値を取得

useDispatchの設定

countの値を更新するためにはAppコンポーネントでAction creatorsを呼び出す必要があります。Action creatorsを実行するためにはdispatchが必要となるためuseDispatchをimportします。Action creatorsはcounterSlice.jsファイルでexportしているのでincreate, decreaseをAppコンポーネントでimportします。


import { useSelector, useDispatch } from 'react-redux';
import { decrease, increase } from './redux/counterSlice';

function App() {
  const count = useSelector((state) => state.counter.count);
  const dispatch = useDispatch();
  return (
    <div>
      <h1>Count: {count}</h1>
      <button onClick={() => dispatch(increase())}>Up</button>
      <button onClick={() => dispatch(decrease())}>Down</button>
    </div>
  );
}

export default App;

Redux Toolkitを設定後のカウンターの画面です。Reduxを利用しない場合と同様にボタンによってCountの値を増減させることができます。

Reduxを利用しない場合のカウンター
Redux Toolkitを利用して作成したカウンター

Reduxを利用しないコードからRedux Toolkitへ書き換え作業は完了です。Reduxを利用しないコードでは他のコンポーネントでcountにアクセスするためにはpropsを利用することができますがReduxを利用している場合はindex.jsのProviderで包んでいるコンポーネント以下にあるコンポーネントからであればどこからでもアクセスすることができます。その時にはuseSelector Hookでアクセスを行い、useDispatch Hookを利用して更新を行います。

Redux DevToolsでの確認

Chromeブラウザを利用している場合はExtentionsのRedux DevToolsをインストールしていればRedux Toolkitを利用して設定したデータの状態を確認することができます。ブラウザのデベロッパーツールを開いてReduxタブを選択することで下記の画面が表示されます。

Redux Devools
Redux Devools

UpボタンをクリックするとCounterSlice.jsのreducersで設定したnameの値とACTIONの名前が左側のパネルに表示されます。右側のパネルのStateタブでは現在のcountの値を確認することができます。Redux DevToolsを見ることで現在のstateやACTIONが設定通りに動作しているか確認することができます。

counterSlice.jsのnameをcounterからcounter_1に変更するとRedux Devtoolsの名前も変わっていることが確認できます。


export const counterSlice = createSlice({
  name: 'counter_1',
  //略

Actionタブを見るとtypeが”counter_1/increate"になっていることが確認できます。

nameを変更した場合
reducerのnameを変更した場合

非同期処理の記述方法

ここでは3つの異なる方法で非同期処理のコードを記述していきます。1つ目はRedux Thunkを利用しない方法、2つ目はRedux Thunkを利用した方法、3つ目はcreateAsyncThunkを利用した方法です。

非同期処理を行うために外部リソースにJSONPLACEHolderを利用します。https://jsonplaceholder.typicode.com/usersにアクセスすると10名分のユーザ情報を取得することができます。

Redux Thunkを利用しない場合

カウンターとは別にユーザ情報を管理するためreduxフォルダの中にuserSlice.jsファイルを作成します。createSliceで設定するname, initialState, reducersの引数は下記のように設定を行います。


import { createSlice } from '@reduxjs/toolkit';

const usersSlice = createSlice({
  name: 'users',
  initialState: {
    users: [],
  },
  reducers: {
    setUsers: (state, action) => {
      state.users = action.payload;
    },
  },
});

export const { setUsers } = usersSlice.actions;

export default usersSlice.reducer;

nameにはusersを設定し、共有するデータusersの初期値は空の配列を設定しています。reducersのsetUsers関数ではdispatchから受け取るpayloadをusersに設定し、自動で作成されるAction CreatorsのsetUsersをexportします。

App.jsファイルではuseEffect Hookを利用してマウント時にJSONSPLACEHolderにアクセスを行いfetch関数を利用してユーザの一覧を取得します。axiosをインストールすればaxiosを利用することも可能です。


import './App.css';
import { useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { decrease, increase } from './redux/counterSlice';
import { setUsers } from './redux/usersSlice';

function App() {
  const count = useSelector((state) => state.counter.count);
  const { users } = useSelector((state) => state.users);
  const dispatch = useDispatch();

  useEffect(() => {
    const getPosts = async () => {
      const res = await fetch('https://jsonplaceholder.typicode.com/users');
      const data = await res.json();
      dispatch(setUsers(data));
    };
    getPosts();
  }, [dispatch]);
  return (
    <div className="App">
      <h1>Count: {count}</h1>
      <button onClick={() => dispatch(increase())}>Up</button>
      <button onClick={() => dispatch(decrease())}>Down</button>
      <h2>User</h2>
      {users && users.map((user, index) => <div key={index}>{user.name}</div>)}
    </div>
  );
}

export default App;

ブラウザ上に表示するユーザ情報usersはuseSelector Hookを使ってstoreから取得します。useEffect内で取得したデータをdispatchで指定したAction CreatorsのsetUsersを使ってstoreに渡しています。

コード記述後にブラウザからアクセスを行い、ユーザ一覧がブラウザ上に表示されることを確認してください。

非同期で取得したユーザ情報を表示
非同期で取得したユーザ情報を表示

Redux Thunkを利用した場合

Reduxで非同期処理にRedux Thunkを利用したい場合はパッケージのインストールとミドルウェアの設定が必要になります。Redux Toolkitではパッケージのインストールもミドルウェアの設定も行うことなくRedux Thunkを利用することができます。

Redux Thunkではdispachの引数に非同期処理を含む関数を指定することができます。非同期処理を含む関数はuserSlice.jsファイルの中で定義します。

先にApp.jsファイルの更新を行います。Redux Thunkを利用しない場合はuseEffectの中に非同期関数を記述しdispatchでAction CreatorsのsetUserを指定していましたが今回はuserSlice.jsからgetUsersをimportしuseEffectのdispatchにgetUsersを指定しています。


import './App.css';
import { useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { decrease, increase } from './redux/counterSlice';
import { getUsers } from './redux/usersSlice';

function App() {
  const count = useSelector((state) => state.counter.count);
  const { users } = useSelector((state) => state.users);
  const dispatch = useDispatch();

  useEffect(() => {
    dispatch(getUsers());
  }, [dispatch]);

  return (
    <div className="App">
      <h1>Count: {count}</h1>
      <button onClick={() => dispatch(increase())}>Up</button>
      <button onClick={() => dispatch(decrease())}>Down</button>
      <h2>User</h2>
      {users && users.map((user, index) => <div key={index}>{user.name}</div>)}
    </div>
  );
}

export default App;

UsersSlice.jsファイルにgetUsers関数を追加します。getUsers関数の中では引数にdispatchを持つ関数を戻していることがわかります。getUsers関数の中でdispatchにAction CreatorsのsetUsersを指定しています。Action CreatorsのsetUsersをexportしていますがこの記述がないとエラーが発生します。


import { createSlice } from '@reduxjs/toolkit';

const usersSlice = createSlice({
  name: 'users',
  initialState: {
    users: [],
  },
  reducers: {
    setUsers: (state, action) => {
      state.users = action.payload;
    },
  },
});

export const getUsers = () => {
  return async (dispatch) => {
    const res = await fetch('https://jsonplaceholder.typicode.com/users');
    const data = await res.json();
    dispatch(setUsers(data));
  };
};

export const { setUsers } = usersSlice.actions;

export default usersSlice.reducer;

ブラウザを確認するとRedux Thunkを利用しない場合と同様にユーザの一覧が表示されます。

非同期で取得したユーザ情報を表示
Redux Thunkを利用して取得したユーザ情報を表示

createAsyncThunkを利用した場合

最後にcreateAsyncThunkを利用した場合の動作確認を行います。createAsyncThunkを利用する場合のApp.jsファイルの内容は同じです。usersSlice.jsからgetUsersをimportしてdispatchの引数にgetUsersを指定しています。


import './App.css';
import { useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { decrease, increase } from './redux/counterSlice';
import { getUsers } from './redux/usersSlice';

function App() {
  const count = useSelector((state) => state.counter.count);
  const { users } = useSelector((state) => state.users);
  const dispatch = useDispatch();
  useEffect(() => {
    dispatch(getUsers());
  }, [dispatch]);
  return (
    <div className="App">
      <h1>Count: {count}</h1>
      <button onClick={() => dispatch(increase())}>Up</button>
      <button onClick={() => dispatch(decrease())}>Down</button>
      <h2>User</h2>
      {users && users.map((user, index) => <div key={index}>{user.name}</div>)}
    </div>
  );
}

export default App;

非同期で外部リソースからデータを取得するcreatAsyncThunk関数はusersSlice.jsファイルの中で利用します。


import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';

export const getUsers = createAsyncThunk('users/getUsers', async () => {
  return await fetch('https://jsonplaceholder.typicode.com/users').then((res) =>
    res.json()
  );
});

createAsyncThunkでは3つの引数を取ることができますがここでは2つの引数のみ利用しています。1つ目はtype、2つ目はpayloadCreator、3つ目はoptionsです。

先に2つ目のpayloadCreatorですがpayloadCreatorにはpromiseを戻す非同期のcallback関数を指定します。

1つ目のtypeには文字列を設定し設定した文字列によって3つのACTION TYPEが作成されます。ここではTypeにusers/getUsersを設定しているので以下の3つのACTION TYPEが作成されます。createAsyncTypeではこのAction Typeが作成されることが重要です。非同期処理のライフサイクルの中でこれらのAction Typeを使うことで各ライフサイクルで別々の処理を行うことができます。

  • pending: ‘users/getUsers/pending’
    pending ActionはpayloadCreatorのcallbackが呼ばれる前にdispatchされます。
  • fulfilled: ‘users/getUsers/fullfiled’
    fullfilled Actionは外部リソースからの情報取得が成功した場合にdispatchされます。
  • rejected: ‘users/getUsers/rejected’
    外部リソースから取得したデータをusersに保存したい場合はfullfilledを利用してextraReducersとして以下のように設定を行います。

外部リソースから取得したデータをusersに保存したい場合はfullfilledを利用してextraReducersとして以下のように設定を行います。fulfilledでは情報の取得が成功しているのでaction.payloadに入っているデータをusersに設定しています。


import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';

export const getUsers = createAsyncThunk('users/getUsers', async () => {
  return await fetch('https://jsonplaceholder.typicode.com/users').then((res) =>
    res.json()
  );
});

const usersSlice = createSlice({
  name: 'users',
  initialState: {
    users: [],
  },
  extraReducers: {
    [getUsers.fulfilled]: (state, action) => {
      state.users = action.payload;
    },
  },
});

export default usersSlice.reducer;

ここまでの設定でブラウザ上にユーザ情報の一覧が表示されます。

非同期で取得したユーザ情報を表示
createAsyncThunkで取得したユーザ情報を表示

fulfilledしか利用していませんでしたが例えばデータ取得中はloadingを表示したいまたエラーが発生したエラー情報を保持したいといった場合にpendingやrejectedを活用することができます。

usersの他に新たににloadingとerrorの変数を追加します。extraReducersの中でfulfilledと同様にpendingとrejectedを追加します。


import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';

export const getUsers = createAsyncThunk('users/getUsers', async () => {
  return await fetch('https://jsonplaceholder.typicode.com/users').then((res) =>
    res.json()
  );
});

const usersSlice = createSlice({
  name: 'users',
  initialState: {
    users: [],
    loading: false,
    error: false,
  },
  extraReducers: {
    [getUsers.pending]: (state) => {
      state.loading = true;
    },
    [getUsers.fulfilled]: (state, action) => {
      state.loading = false;
      state.users = action.payload;
    },
    [getUsers.rejected]: (state) => {
      state.loading = false;
      state.error = true;
    },
  },
});

export default usersSlice.reducer;

getUsers.pendingでloadingの値をfalseからtrueに変更します。fulfilledとデータ取得に成功するかrejectedでデータ取得に失敗するまではloadingの値はtrueのままになります。データ取得に成功すればloadingの値をfalseに戻し、取得したデータをusersに保存します。データ取得に失敗すればloadingの値をfalseに戻し、errorの値をtrueに変更します。

App.jsファイルでuseSelector Hookから追加したloading, errorを取得します。loadingがtrueの場合は画面に”loading”、errorがtrueの場合は画面に”データ取得に失敗しました”が表示されるように設定します。


import './App.css';
import { useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { decrease, increase } from './redux/counterSlice';
import { getUsers } from './redux/usersSlice';

function App() {
  const count = useSelector((state) => state.counter.count);
  const { users, loading, error } = useSelector((state) => state.users);
  const dispatch = useDispatch();
  useEffect(() => {
    dispatch(getUsers());
  }, [dispatch]);
  return (
    <div className="App">
      <h1>Count: {count}</h1>
      <button onClick={() => dispatch(increase())}>Up</button>
      <button onClick={() => dispatch(decrease())}>Down</button>
      <h2>User</h2>
      {loading && <p>Loading</p>}
      {error && <p>データ取得に失敗しました。</p>}
      {users && users.map((user, index) => <div key={index}>{user.name}</div>)}
    </div>
  );
}

export default App;

ブラウザをリロードすると一瞬loadingが表示された後にユーザ一覧が表示されます。意図的にfetch関数で指定したURLのドメイン名から1文字変更してブラウザをリロードすると”データ取得に失敗しました”が表示されます。createAsyncThunkを利用することでただデータを非同期に取得するだけではなくLoadingやエラーを表示するといった仕組みを簡単に実装することができます。

TypeScript

Viteを利用してTypeScript環境のReactプロジェクトを作成しますが作成するコードは先ほど作成したカウンターのコードにTypeScriptの型を追加しています。

環境の構築

Redux ToolkitをTypeScriptで記述した場合のコードについて確認しておきます。ここではReactのプロジェクトはViteを利用して作成します。プロジェクト名は任意の名前のvite-redux-toolkitとします。利用するフレームワークはReact、TypeScriptを選択します。


 % npm create vite@latest

✔ Project name: … vite-redux-toolkit
✔ Select a framework: › React
✔ Select a variant: › TypeScript

Scaffolding project in /Users/mac/Desktop/vite-redux-toolkit...

Done. Now run:

  cd vite-redux-toolkit
  npm install
  npm run dev

プロジェクトフォルダに移動してnpm installコマンドを実行します。


 % cd vite-redux-toolkit
 % npm install

Reduxとredux toolkitパッケージのインストールを行います。


 % npm install @reduxjs/toolkit react-redux

Sliceファイルの作成

srcフォルダ直下にreduxフォルダを作成してcounterSlice.tsファイルを作成して以下のコードを記述します。


import { createSlice } from '@reduxjs/toolkit';

type Counter = {
  count: number;
};

const initialState: Counter = {
  count: 0,
};

const counterSlice = createSlice({
  name: 'counter',
  initialState,
  reducers: {
    increase: (state) => {
      state.count += 1;
    },
    decrease: (state) => {
      state.count -= 1;
    },
  },
});

export const { increase, decrease } = counterSlice.actions;

export default counterSlice.reducer;

Storeの作成

reduxフォルダの中にstore.tsファイルを作成しcounterSliceからcounterReducerをimportしてconfigureStoreに追加します。


import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './counterSlice';

export const store = configureStore({
  reducer: {
    counter: counterReducer,
  },
});

export type AppDispatch = typeof store.dispatch;
export type RootState = ReturnType<typeof store.getState>;

Providerの設定

main.tsxファイルでProvierでAppコンポーネントを包みます。


import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App.tsx';
import './index.css';
import { store } from './redux/store';
import { Provider } from 'react-redux';

ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
  <React.StrictMode>
    <Provider store={store}>
      <App />
    </Provider>
  </React.StrictMode>
);

App.tsxファイルの更新

App.tsxファイルの中でカウンターの設定を行います。


import { useSelector, useDispatch, TypedUseSelectorHook } from 'react-redux';
import { decrease, increase } from './redux/counterSlice';
import type { RootState, AppDispatch } from './redux/store';
import './App.css';

export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
export const useAppDispatch = () => useDispatch<AppDispatch>();

function App() {
  const count = useAppSelector((state) => state.counter.count);
  const dispatch = useAppDispatch();
  return (
    <div className="App">
      <h1>Count: {count}</h1>
      <button onClick={() => dispatch(increase())}>Up</button>
      <button onClick={() => dispatch(decrease())}>Down</button>
    </div>
  );
}

export default App;

npm run devコマンドを起動して開発サーバを起動します。ブラウザでアクセスするとカウンターが表示されるのでUpボタンをクリックすると1増え、Downボタンをクリックすると1減ります。

Counterの表示
Counterの表示

Redux ToolkitでのTypeScriptを利用した記述方法が確認できました。