Make it to make it

いろいろ作ってアウトプットするブログ

Redux Toolkitのレビュー

mktmkt.hatenablog.com

前回の投稿で、従来のReact, Reduxで書いていた部分をRedux Toolkit (RTK) でリファクタしたが、今回はRTKのメソッドについてまとめていく。

Redux Toolkit (RTK) のメソッド一覧

configureStore()

configureStore()に渡すオプションの型定義を見てみる。

/**
 * Options for `configureStore()`.
 *
 * @public
 */
export declare interface ConfigureStoreOptions<S = any, A extends Action = AnyAction, M extends Middlewares<S> = Middlewares<S>> {
    /**
     * A single reducer function that will be used as the root reducer, or an
     * object of slice reducers that will be passed to `combineReducers()`.
     */
    reducer: Reducer<S, A> | ReducersMapObject<S, A>;
    /**
     * An array of Redux middleware to install. If not supplied, defaults to
     * the set of middleware returned by `getDefaultMiddleware()`.
     */
    middleware?: M;
    /**
     * Whether to enable Redux DevTools integration. Defaults to `true`.
     *
     * Additional configuration can be done by passing Redux DevTools options
     */
    devTools?: boolean | EnhancerOptions;
    /**
     * The initial state, same as Redux's createStore.
     * You may optionally specify it to hydrate the state
     * from the server in universal apps, or to restore a previously serialized
     * user session. If you use `combineReducers()` to produce the root reducer
     * function (either directly or indirectly by passing an object as `reducer`),
     * this must be an object with the same shape as the reducer map keys.
     */
    preloadedState?: DeepPartial<S extends any ? S : S>;
    /**
     * The store enhancers to apply. See Redux's `createStore()`.
     * All enhancers will be included before the DevTools Extension enhancer.
     * If you need to customize the order of enhancers, supply a callback
     * function that will receive the original array (ie, `[applyMiddleware]`),
     * and should return a new array (such as `[applyMiddleware, offline]`).
     * If you only need to add middleware, you can use the `middleware` parameter instaead.
     */
    enhancers?: StoreEnhancer[] | ConfigureEnhancersCallback;
}

reducerが必須のオプションで、それ以外のmiddleware, devTools, preloadedState, enhancersは任意となっている。

reducer部分には、複数のreducerを従来のcombineReducerの要領でオブジェクト形式で組み合わせて入れることもできれば、単体のreducerを入れることもできる。

combineReducerでラップして渡すこともできるけど不要。

export default configureStore({
  reducer: {
    todos: todosSlice.reducer,
    selectedTodo: selectedTodoSlice.reducer,
    counter: counterSlice.reducer,
  }
})

export default configureStore({
  reducer: todosSlice.reducer
})

middlewareは、何も渡さなければgetDefaultMiddleware()が適用される。

もし何らかの理由でそれらのgetDefaultMiddleware()の適用をしたくなければ、空配列を渡せばよい。

もしくはgetDefaultMiddleware()に足すかたちでミドルウェアを加えてあげてもよい。

またcombineReducerでのラップが不要であるのと同じように、applyMiddlewareでラップする必要もない。

export default configureStore({
  reducer: {
    // ...
  },
  middleware: [],
})

export default configureStore({
  reducer: {
    // ...
  },
  middleware: [...getDefaultMiddleware(), logger],
})

RTKではdevToolsも合わせてついてくるので、出し分け制御もできる。

export default configureStore({
  reducer: {
    // ...
  },
  middleware: : {
    // ...
  },
  devTools: process.env.NODE_ENV !== 'production'
})

大体ここまででほとんどカバーできるだろう。

createSlice()

createSlice()に渡すオプションの型定義を見てみる。

/**
 * Options for `createSlice()`.
 *
 * @public
 */
export declare interface CreateSliceOptions<State = any, CR extends SliceCaseReducers<State> = SliceCaseReducers<State>> {
    /**
     * The slice's name. Used to namespace the generated action types.
     */
    name: string;
    /**
     * The initial state to be returned by the slice reducer.
     */
    initialState: State;
    /**
     * A mapping from action types to action-type-specific *case reducer*
     * functions. For every action type, a matching action creator will be
     * generated using `createAction()`.
     */
    reducers: ValidateSliceCaseReducers<State, CR>;
    /**
     * A mapping from action types to action-type-specific *case reducer*
     * functions. These reducers should have existing action types used
     * as the keys, and action creators will _not_ be generated.
     * Alternatively, a callback that receives a *builder* object to define
     * case reducers via calls to `builder.addCase(actionCreatorOrType, reducer)`.
     */
    extraReducers?: CaseReducers<NoInfer<State>, any> | ((builder: ActionReducerMapBuilder<NoInfer<State>>) => void);
}

公式ドキュメントにはcreateSliceについて下記のように書いてある。

A function that accepts an initial state, an object full of reducer functions, and a "slice name", and automatically generates action creators and action types that correspond to the reducers and state.

action typeはslice名の後にスラッシュ区切りで、合わせて自動で生成してくれる。

initialStateも必須となっているので、従来のように初期のstateの型と値を与えることを忘れることもなくなる。

また、ここが一番の利点思っている点として、immerによりstateをmutableにハンドルすることができるようになる。

だた、immerを使用する際の注意点として、

  • immerはあくまで、mutableにinputされた書き方をうまくハンドリングしてimmutableに処理してくれているだけ
  • プリミティブ型に対してはmutateできない
  • stateのmutateをするか もしくは 新しいstateを返すか のどちらかのみ

もう一つ、prepareを使用することでactionに渡す前に処理を一回挟めるので、予め渡すオブジェクトを用意する場合などに使える。