0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

configureStoreで引数に渡したreducerが初期化される流れ

Posted at

①configureStore関数の実装

node_modules/@reduxjs/toolkit/src/configureStore.ts
/** 116行目 */
export function configureStore<
  S = any,
  A extends Action = UnknownAction,
  M extends Tuple<Middlewares<S>> = Tuple<[ThunkMiddlewareFor<S>]>,
  E extends Tuple<Enhancers> = Tuple<
    [StoreEnhancer<{ dispatch: ExtractDispatchExtensions<M> }>, StoreEnhancer]
  >,
  P = S,
>(options: ConfigureStoreOptions<S, A, M, E, P>): EnhancedStore<S, A, E> {
  const getDefaultMiddleware = buildGetDefaultMiddleware<S>()

  const {
    reducer = undefined,
    middleware,
    devTools = true,
    preloadedState = undefined,
    enhancers = undefined,
  } = options || {}

  let rootReducer: Reducer<S, A, P>

  if (typeof reducer === 'function') {
    rootReducer = reducer
  } else if (isPlainObject(reducer)) {
    rootReducer = combineReducers(reducer) as unknown as Reducer<S, A, P>
  } else {
    throw new Error(
      '`reducer` is a required argument, and must be a function or an object of functions that can be passed to combineReducers',
    )
  }

/** 以下略 */

以下の部分でcombineReducers関数を呼び出し、Reducerの初期化を行っています。

node_modules/@reduxjs/toolkit/src/configureStore.ts
  } else if (isPlainObject(reducer)) {
    rootReducer = combineReducers(reducer) as unknown as Reducer<S, A, P>
  }

以下でcombineReducers関数をimportしており、combineReducers関数がreduxライブラリの関数であることが確認できます。

node_modules/@reduxjs/toolkit/src/configureStore.ts
/** 1行目 */
import type {
  Reducer,
  ReducersMapObject,
  Middleware,
  Action,
  StoreEnhancer,
  Store,
  UnknownAction,
} from 'redux'
import {
  applyMiddleware,
  createStore,
  compose,
  combineReducers,
  isPlainObject,
} from 'redux'

/** 以下略 */

②combineReducers関数の実装

node_modules/redux/src/combineReducers.ts
/** 123行目 */
export default function combineReducers(reducers: {
  [key: string]: Reducer<any, any, any>
}) {
  const reducerKeys = Object.keys(reducers)
  const finalReducers: { [key: string]: Reducer<any, any, any> } = {}
  for (let i = 0; i < reducerKeys.length; i++) {
    const key = reducerKeys[i]

    if (process.env.NODE_ENV !== 'production') {
      if (typeof reducers[key] === 'undefined') {
        warning(`No reducer provided for key "${key}"`)
      }
    }

    if (typeof reducers[key] === 'function') {
      finalReducers[key] = reducers[key]
    }
  }
  const finalReducerKeys = Object.keys(finalReducers)

  // This is used to make sure we don't warn about the same
  // keys multiple times.
  let unexpectedKeyCache: { [key: string]: true }
  if (process.env.NODE_ENV !== 'production') {
    unexpectedKeyCache = {}
  }

  let shapeAssertionError: unknown
  try {
    assertReducerShape(finalReducers)
  } catch (e) {
    shapeAssertionError = e
  }
  
/** 以下略 */

以下の部分でassertReducerShape関数を呼び出し、Reducerの初期化を行っています。

node_modules/@reduxjs/toolkit/src/configureStore.ts
  try {
    assertReducerShape(finalReducers)
  } catch (e) {
    shapeAssertionError = e
  }

以下でassertReducerShape関数を定義しています。

node_modules/redux/src/combineReducers.ts
/** 62行目 */
function assertReducerShape(reducers: {
  [key: string]: Reducer<any, any, any>
}) {
  Object.keys(reducers).forEach(key => {
    const reducer = reducers[key]
    const initialState = reducer(undefined, { type: ActionTypes.INIT })

    if (typeof initialState === 'undefined') {
      throw new Error(
        `The slice reducer for key "${key}" returned undefined during initialization. ` +
          `If the state passed to the reducer is undefined, you must ` +
          `explicitly return the initial state. The initial state may ` +
          `not be undefined. If you don't want to set a value for this reducer, ` +
          `you can use null instead of undefined.`
      )
    }

    if (
      typeof reducer(undefined, {
        type: ActionTypes.PROBE_UNKNOWN_ACTION()
      }) === 'undefined'
    ) {
      throw new Error(
        `The slice reducer for key "${key}" returned undefined when probed with a random type. ` +
          `Don't try to handle '${ActionTypes.INIT}' or other actions in "redux/*" ` +
          `namespace. They are considered private. Instead, you must return the ` +
          `current state for any unknown actions, unless it is undefined, ` +
          `in which case you must return the initial state, regardless of the ` +
          `action type. The initial state may not be undefined, but can be null.`
      )
    }
  })
}

/** 以下略 */

下記の通り、オブジェクト内のreducerに対し、init処理を行っていることが確認できます。

node_modules/redux/src/combineReducers.ts
  Object.keys(reducers).forEach(key => {
    const reducer = reducers[key]
    const initialState = reducer(undefined, { type: ActionTypes.INIT })

おわりに

storeにreducerを渡しただけで使えるようになる、というredux-toolkitの仕様にやや困惑したので、ソースをさかのぼって確認してみました。
自分の目ではソースを読み切るのが難しく、LLMと一緒に作業してみました。いい経験になりました!

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?