「Redux Toolkit( createSlice
configureStore
)」と「Hooks API( useSelector
useDispatch
)」を利用したケースで、ReduxをReactに導入する方法について確認します。
目次
前準備
create-react-app
でReactアプリを作成して、必要なパッケージをインストールします。
(TypeScriptで動作確認します。)
npx create-react-app my-app --template typescript
cd my-app/
yarn add redux react-redux @reduxjs/toolkit
yarn add --dev @types/react-redux
フォルダ構造
以下フォルダ構成で構築したReactアプリにて動作確認をしました。
src/
├── components
│ └── Sample.tsx // 「Hooks API(useDispatch, useSelector)」 と 「各SliceのAction Creator」 で 「Store」 に接続
├── features
│ ├── counter.ts // Redux Toolkitの 「createSlice」 で 「State, Action Creator, Reducer」 をまとめて作成
│ └── log.ts // Redux Toolkitの 「createSlice」 で 「State, Action Creator, Reducer」 をまとめて作成
├── App.tsx
├── index.tsx // React Reduxの 「Providerコンポーネント」 を利用して、ReactアプリでReduxを利用できるようにする
└── store.ts // 「各SliceのReducer」 をまとめて 「Store」 を生成
Redux ToolkitのcreateSlice
( State
Action Creator
Reducer
を作成 )
Redux Toolkitの createSlice
を利用すると State
Action Creator
Reducer
をまとめて作成することができます。
今回は2つのsliceを作成します。
src/features/counter.ts
import { createSlice } from '@reduxjs/toolkit';
export type CounterState = {
count: number;
};
const initialState: CounterState = { count: 0 };
export const counterSlice = createSlice({
name: 'counter',
initialState,
reducers: {
incrementCount: (state) => {
return { ...state, count: state.count + 1 };
},
decrementCount: (state) => {
return { ...state, count: state.count - 1 };
},
},
});
src/features/log.ts
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
type Log = {
id: number;
text: string;
};
export type LogState = {
logs: Log[];
loading: boolean;
};
const initialState: LogState = {
logs: [],
loading: false,
};
export const logSlice = createSlice({
name: 'log',
initialState,
reducers: {
addLog: (state, action: PayloadAction<Log>) => {
return {
...state,
logs: [...state.logs, action.payload],
loading: false,
};
},
deleteLog: (state, action: PayloadAction<Pick<Log, 'id'>>) => {
return {
...state,
logs: state.logs.filter((log) => log.id !== action.payload.id),
loading: false,
};
},
setLogLoading: (state) => {
return {
...state,
loading: true,
};
},
},
});
Redux ToolkitのconfigureStore
( Storeを作成 )
src/store.ts
combineReducers
で各Sliceのreducerをまとめた後、configureStore
でStoreを作成します。
import { combineReducers } from 'redux';
import { configureStore } from '@reduxjs/toolkit';
import { counterSlice, CounterState } from 'features/counter';
import { logSlice, LogState } from 'features/log';
export type AppState = {
counter: CounterState;
log: LogState;
};
const rootReducer = combineReducers<AppState>({
counter: counterSlice.reducer,
log: logSlice.reducer,
});
const store = configureStore({ reducer: rootReducer });
export default store;
React Reduxの Provider
( ReactアプリでReduxを利用 )
src/index.tsx
React Reduxの Provider
を利用して、ReactアプリでReduxを利用できるようにします。
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import App from 'App';
import store from 'store';
ReactDOM.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>,
document.getElementById('root'),
);
Hooks API( useDispatch
useSelector
)
( ComponentからStoreに接続 )
以前は、React Reduxの connect
を呼び出し、Storeに接続していました。
Hooks API( useDispatch
useSelector
)を利用してStoreに接続できるようになりました。
src/App.tsx
import React, { FC } from 'react';
import Sample from 'components/Sample';
const App: FC = () => {
return (
<div style={{ padding: '12px', width: '400px', backgroundColor: '#ccc' }}>
<h1>App</h1>
<Sample samplePropData="Data passed from parent." />
</div>
);
};
export default App;
src/components/Sample.tsx
import React, { FC } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { counterSlice } from 'features/counter';
import { logSlice, LogState } from 'features/log';
import { AppState } from 'store';
type Props = {
samplePropData?: string;
};
const Sample: FC<Props> = ({ samplePropData }) => {
const { count, log } = useSelector<
AppState,
{ count: number; log: LogState }
>((state) => ({
count: state.counter.count,
log: state.log,
}));
const dispatch = useDispatch();
const { incrementCount, decrementCount } = counterSlice.actions;
const { addLog, deleteLog, setLogLoading } = logSlice.actions;
return (
<div style={{ padding: '12px', backgroundColor: '#eee' }}>
<h1>Sample Component</h1>
<p>samplePropData: {samplePropData}</p>
<h2>Counter State</h2>
<p>count: {count}</p>
<button type="button" onClick={() => dispatch(incrementCount())}>
incrementCount
</button>
<button type="button" onClick={() => dispatch(decrementCount())}>
decrementCount
</button>
<h2>Log State</h2>
<p>log.logs.length: {log.logs.length}</p>
<p>log.loading: {log.loading.toString()}</p>
<button
type="button"
onClick={() => dispatch(addLog({ id: 1, text: 'xxx' }))}
>
addLog
</button>
<button type="button" onClick={() => dispatch(deleteLog({ id: 1 }))}>
deleteLog
</button>
<button type="button" onClick={() => dispatch(setLogLoading())}>
setLogLoading
</button>
</div>
);
};
export default Sample;
- 状態の取得
useSelector
でStoreから状態を取得します。
- 状態の更新
- 各SliceからAction Creator関数を取得してます。
useDispatch
の戻り値で取得した関数を経由してActionをdispatchしてます。
動作確認
Redux Devtools を利用して動作確認します。
参考
- Redux Toolkit
- useSelector, useDispatch
- https://redux.js.org/style-guide/style-guide#structure-files-as-feature-folders-or-ducks
- https://react-redux.js.org/api/provider