Redux Toolkitをまとめてみた
React hooksも素晴らしいですが、様々な面で限界があるので状態管理ライブラリを導入した方がいいと思います。その中ではやっぱりまだReduxが最有力な選択肢でしょう。
今回はreduxと併用するRedux Toolkitについて勉強したので、今回まとめてみました。
この記事の概要
この記事は「Reduxの理解はあるが、Redux Toolkitと併用したことない」という人に向けて、Redux Toolkitを使うために必要な全体像の理解をざっとするというのが目的です。自分の勉強のログ残しも目的
redux toolkitのメリット
以下documentから引用したものです。
The Redux Toolkit package is intended to be the standard way to write Redux logic. It was originally created to help address three common concerns about Redux:
- "Configuring a Redux store is too complicated"
- "I have to add a lot of packages to get Redux to do anything useful"
- "Redux requires too much boilerplate cod
ざっくり要約すると
- Storeの理解を簡単にする
- パッケージをたくさん入れずにすむようにする
- 定型の繰り返されるコードを削減する
この3つがRedux Toolkitの目的と言えるでしょう。確かにActionに莫大なswitch文を書くのはとてもめんどくさいです。
These tools should be beneficial to all Redux users.
Reduxを使う人にとって、とても有益なpackageです。
install
今回は既存のアプリに適用させる場合のみ言及します。
// npm
npm install @reduxjs/toolkit react-redux
// yarn
yarn add @reduxjs/toolkit react-redux
実際の使い方
基本的にはsliceとasyncAction、そしてstoreを作成すればOKです。
ReduxではActionとReducerが分かれていたと思いますが、Toolkitを使いSliceで一元化することができます。
slice
このコードではユーザーのサインアウトを想定しています。単純にuserのstateを初期値に戻します。
createSliceを使うことでsliceとその内部でreducerを設定することができます。
import { createSlice, PayloadAction } from '@reduxjs/toolkit'
export type User = {
id: string | null
username: string | null
password: string | null
}
export type UserState = {
user: User
}
export type UpdateUserPayload = User
export type AddHistoryPayload = string
export const initialState: UserState = {
user: {
id: null,
username: null,
password: null,
},
}
const userSlice = createSlice({
name: 'user',
initialState,
reducers: {
signOut() {
return initialState
},
},
})
export default userSlice
store
先ほど作成したuserSliceのreducerをstoreのreducerに設定してください。
加えてsliceの数が複数になった際はcombineReducersで合体させた方が良いです。
import { Store } from 'redux';
import logger from 'redux-logger';
import { configureStore, getDefaultMiddleware } from '@reduxjs/toolkit';
import userSlice, { initialState as userState } from './users/slice';
const preloadedState = () => {
return userState;
};
export type StoreState = ReturnType<typeof preloadedState>;
export type ReduxStore = Store<StoreState>;
const middlewareList = [...getDefaultMiddleware(), logger];
const createStore = configureStore({
reducer: userSlice.reducer,
middleware: middlewareList,
preloadedState: preloadedState(),
devTools: true,
});
export default createStore;
middlewareなどの必要なものは各自で設定して下さい。
createAsyncThunk
createAsyncThunkは非同期処理のreducerと捉えてください。先ほどのsliceに追記します。
また、通信のステータス(pending
,fulfilled
,rejected
)ごとに処理を条件分岐して記述することもできます。
import { createSlice, PayloadAction } from '@reduxjs/toolkit'
import { signIn,signUp } from './asyncAction'
export type User = {
id?: string | null
username: string | null
password: string | null
}
export type UserState = {
user: User
}
export type UpdateUserPayload = User
export type AddHistoryPayload = string
export const initialState: UserState = {
user: {
id: null,
username: null,
password: null,
},
}
const userSlice = createSlice({
name: 'user',
initialState,
reducers: {
signOut() {
return initialState
},
},
// 追記------------------------------------
extraReducers: (builder) => {
// signUp
builder.addCase(signUp.rejected,(state, action) => {
console.log(action.error.message);
});
builder.addCase(signUp.fulfilled, (state, action: PayloadAction<UserState>) => {
state.user = {
id: action.payload.user.id,
username: action.payload.user.username,
password: action.payload.user.password
}
});
// signIn
builder.addCase(signIn.rejected,(state, action) => {
console.log(action.error.message);
});
builder.addCase(signIn.fulfilled, (state, action: PayloadAction<UserState>) => {
state.user = {
id: action.payload.user.id,
username: action.payload.user.username,
password: action.payload.user.password
}
})
}
// ここまで------------------------------------
})
export default userSlice
asyncActionファイルを作成して、そこに処理を記述しました。
今回はサッと試すためにrails APIモードをバックエンドにしてjsonでデータをフェッチしてくるようにしました。とりあえずRedux Toolkitの挙動確認のためにサッとやったので細かい部分のご指摘は遠慮ください。
import { createAsyncThunk } from '@reduxjs/toolkit';
import { UserState } from './slice';
export const signIn = createAsyncThunk(
'users/signIn',
async (arg: UserState): Promise<UserState> => {
const response = await fetch(
"http://localhost:3000/users/signIn", {
method: "POST",
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(arg)
});
const json = await response.json();
// reducerへ
return {
user: json
};
}
);
export const signUp = createAsyncThunk(
'users/signUp',
async (arg: UserState): Promise<UserState> => {
const response = await fetch(
"http://localhost:3000/users/signUp", {
method: "POST",
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(arg)
});
const json = await response.json();
// reducerへ
return {
user: json
};
}
);
コンポーネントから呼び出すには
画面側では関数を定義してあげて、dispatchで呼び出せばOK.
const onclickSignIn = (params: UserState) => {
dispatch(signIn(params));
}
単純に値を使いたい時もuseSelector
を使えばOK.
const user = useSelector((state: UserState) => state.user);
Storeをラッピングしてあげよう。
もちろん状態をコンポーネント間で管理できるようにして下さい。
Provider と createStoreをimportした上で子コンポーネントに渡せばOK.
import { Provider } from 'react-redux';
import createStore from '../redux/createStore';
-----------------------------------------------
<Provider store={createStore}>
<Component {...pageProps} />
</Provider>
参考にしたサイト
参考になれば幸いです。
Discussion