Redux - Toolkit
QuickStart
目的
- Redux Toolkit は、redux に関する下記の問題を解決するためのツール
- 初期セットアップが複雑すぎる
- 便利に使うには多くのパッケージをインストールする必要がある
- ボイラープレートが多すぎる
- 全てのケースはカバー出来なくても、大半のケースでコードを簡略化できるツールを目指している
create-react-app
やapollo-boost
の精神に感化されている
含まれるもの
configureStore()
createReducer()
createAction()
createSlice()
createAsyncThunk()
createEntityAdapter
createSelector
---reselect
のcreateSelector
を利便性のために再エクスポートしたもの
基本チュートリアル
configureStore
createStore()
のラッパー- reducer をオブジェクトとして与えた場合には
combineReducer()
が自動で呼ばれる - ミドルウェア群がデフォルトで追加される
- Redux DevTools
- redux-thunk
- Immutability check middleware --- store の値が直接改変されないよう監視する
- Serializability check middleware --- シリアライズ出来ない値()が store に入り込まないように監視する
// Before:
const store = createStore(counter);
// After:
const store = configureStore({
reducer: counter,
});
createAction
- 与えられた action type (文字列)を元に action creator を生成する
- 生成された action creator 関数は
toString()
メソッドを持つため、そのまま action type としても使用できる - 本当は
createActionCreator()
が正しい名前である
action type の取得方法は 2 つある
- オーバーライドされた
toString()
を使う(action creator をそのまま使う) .type
を使う
// Before
const INCREMENT = 'INCREMENT';
const DECREMENT = 'DECREMENT';
function increment() {
return { type: INCREMENT };
}
function decrement() {
return { type: DECREMENT };
}
function counter(state = 0, action) {
switch (action.type) {
case INCREMENT:
return state + 1;
case DECREMENT:
return state - 1;
default:
return state;
}
}
// After
const increment = createAction('INCREMENT');
const decrement = createAction('DECREMENT');
function counter(state = 0, action) {
switch (action.type) {
// .typeを使う方法
case increment.type:
return state + 1;
// toStringを使う方法
case decrement:
return state - 1;
default:
return state;
}
}
createReducer
- slice reducer を作成して返す
- root reducer --- store 全体を管理する reducer (アプリ単位。多くの場合
combineReducers()
で作られる reducer) - slice reducer --- store の一部を管理する reducer (ドメイン単位。例:
store.todos
を管理する reducer) - case reducer --- 個々のアクションに対応する reducer (アクション単位。例:
ADD_TODO
を担当する reducer)
- root reducer --- store 全体を管理する reducer (アプリ単位。多くの場合
- action type から reducer へのルックアップテーブルという形で処理を記述できるようにする。これにより switch 文が不要になる。
- 裏側で immer が使われているため、state を直接書き換える形で値を改変することができることにより、簡潔な表記が可能になる
- デフォルトケースについては明記する必要はない
- TypeScript 環境では型推論の効く builder パターンでの記述を推奨
const increment = createAction('INCREMENT');
const decrement = createAction('DECREMENT');
// object記法
// - action変数に手動で型付けが必要
// - アクション名を参照するときに`.type`が必要になる場合あり
const counter = createReducer(0, {
// toStringを使う方法
[increment]: (state, action) => {},
// .typeを使う方法
[decrement.type]: (state, action) => {},
});
// builder記法
// - action変数には自動で型推論が効いている
// - アクション名を参照するときに`.type`は不要
const counter = createReducer(0, (builder) =>
builder
.addCase(increment, (state, action) => {})
.addCase(decrement, (state, action) => {}),
);
createSlice
- slice オブジェクトを作成する(state の一部を管理する責務をもつオブジェクト)
- 下記を一括で生成する
- reducer
- action creators (reducer オブジェクトのキー名が関数名となる)
- action type strings (slice 名 + reducer オブジェクトのキー名)
- 引数
- slice 名
- store の初期値
- reducers オブジェクト
- 多くの場合
createAction()
やcreateReducer()
を使うまでもなくcreateSlice()
だけで事足りる
const counterSlice = createSlice({
name: 'counter',
initialState: 0,
reducers: {
increment: (state) => (state += 1),
decrement: (state) => (state -= 1),
},
});
const { actions, reducer } = counterSlice;
const { increment, decrement } = actions;
// createSlice()の返値は下記のような形式になる
// {
// name: "todos",
// reducer: (state, action) => newState,
// actions: {
// addTodo: (payload) => ({type: "todos/addTodo", payload}),
// toggleTodo: (payload) => ({type: "todos/toggleTodo", payload})
// },
// caseReducers: {
// addTodo: (state, action) => newState,
// toggleTodo: (state, action) => newState,
// }
// }
extraReducers
- action creator を自動的に作成したくない場合に使用する
- action creator が作成されない点を除いて
reducers
と同じ働きをする - TypeScript 環境では型推論の効く 前述の builder 記法での記述を推奨
const counterSlice = createSlice({
name: 'counter',
initialState: 0,
// object記法
extraReducers: {
[increment]: (state, action) => {},
[decrement]: (state, action) => {},
},
// builder記法
extraRducers: (builder) =>
builder
.addCase(increment, (state, action) => {})
.addCase(decrement, (state, action) => {}),
});