createStore第一个参数reducer

Posted bowennan

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了createStore第一个参数reducer相关的知识,希望对你有一定的参考价值。

       上一篇解读了 createStore 方法,重点以createStore 返回值 getState, dispatch 以及 subscribe  为知识点,接下来我们聊聊参数。

       首先提一个问题:redux中包含可以包含几个reducer?答案是:1个。因为参数只有一个接收reducer,如果你有多个reducer,最后也要通过combineReducers合并为一个 。reducer简单来说是一个方法,接受初始值state和指示值action两个参数:

function testReducer (state, action) {
   if (typeof state === ‘undefined‘) {
      state = ‘ ‘;  //初始值
   }
   if (action.type === ‘eat‘) {
      return state = eatSomething
   }
   if (action.type === ‘drink‘) {
      return state = drinkSomething
   }
} 

 

action较多时,可以使用 switch语句替代if语句,初始值可以使用ES6的写法
function testReducer (state = ‘none‘, action) {
   switch (action.type) {
     case ‘eat‘:
        return state = ‘eatSomething‘;
     case ‘drink‘:
        return state = ‘drinkSomething‘;
     case ‘read‘:
        return state = ‘readSomething‘;
     default:
        return state;
   }
}

 

下面把吃喝阅读拆分成单个的reducer

 


//按照标准的ES6的写法
const eatSomething = (state = ‘none‘, action = {}) => {
  const {type, payload} = action;
  if (type === ‘eat‘) {
    return state = payload;
  } eles {
    return state;
  }
}

const drinkSomething = (state = ‘none‘, action = {}) => {
  const {type, payload} = action;
  if (type === ‘drink‘) {
    return state = payload;
  } eles {
    return state;
  }
}

const readSomething = (state = ‘none‘, action = {}) => {
  const {type, payload} = action;
  if (type === ‘read‘) {
    return state = payload;
  } eles {
    return state;
  }
}

将上面三个reducer合并为一个reducers

const reducers = combineReducers({
  eatSomething,
  drinkSomething,
  readSome
})

说到这里我去看看 combineReducers的源码

  1 function getUndefinedStateErrorMessage(key, action) {
  2   const actionType = action && action.type
  3   const actionDescription =
  4     (actionType && `action "${String(actionType)}"`) || ‘an action‘
  5 
  6   return (
  7     `Given ${actionDescription}, reducer "${key}" returned undefined. ` +
  8     `To ignore an action, you must explicitly return the previous state. ` +
  9     `If you want this reducer to hold no value, you can return null instead of undefined.`
 10   )
 11 }
 12 
 13 function getUnexpectedStateShapeWarningMessage(
 14   inputState,
 15   reducers,
 16   action,
 17   unexpectedKeyCache
 18 ) {
 19   const reducerKeys = Object.keys(reducers)
 20   const argumentName =
 21     action && action.type === ActionTypes.INIT
 22       ? ‘preloadedState argument passed to createStore‘
 23       : ‘previous state received by the reducer‘
 24 
 25   if (reducerKeys.length === 0) {
 26     return (
 27       ‘Store does not have a valid reducer. Make sure the argument passed ‘ +
 28       ‘to combineReducers is an object whose values are reducers.‘
 29     )
 30   }
 31 
 32   if (!isPlainObject(inputState)) {
 33     return (
 34       `The ${argumentName} has unexpected type of "` +
 35       {}.toString.call(inputState).match(/\\s([a-z|A-Z]+)/)[1] +
 36       `". Expected argument to be an object with the following ` +
 37       `keys: "${reducerKeys.join(‘", "‘)}"`
 38     )
 39   }
 40 
 41   const unexpectedKeys = Object.keys(inputState).filter(
 42     key => !reducers.hasOwnProperty(key) && !unexpectedKeyCache[key]
 43   )
 44 
 45   unexpectedKeys.forEach(key => {
 46     unexpectedKeyCache[key] = true
 47   })
 48 
 49   if (action && action.type === ActionTypes.REPLACE) return
 50 
 51   if (unexpectedKeys.length > 0) {
 52     return (
 53       `Unexpected ${unexpectedKeys.length > 1 ? ‘keys‘ : ‘key‘} ` +
 54       `"${unexpectedKeys.join(‘", "‘)}" found in ${argumentName}. ` +
 55       `Expected to find one of the known reducer keys instead: ` +
 56       `"${reducerKeys.join(‘", "‘)}". Unexpected keys will be ignored.`
 57     )
 58   }
 59 }
 60 
 61 function assertReducerShape(reducers) {
 62   Object.keys(reducers).forEach(key => {
 63     const reducer = reducers[key]
 64     const initialState = reducer(undefined, { type: ActionTypes.INIT })
 65 
 66     if (typeof initialState === ‘undefined‘) {
 67       throw new Error(
 68         `Reducer "${key}" returned undefined during initialization. ` +
 69           `If the state passed to the reducer is undefined, you must ` +
 70           `explicitly return the initial state. The initial state may ` +
 71           `not be undefined. If you don‘t want to set a value for this reducer, ` +
 72           `you can use null instead of undefined.`
 73       )
 74     }
 75 
 76     if (
 77       typeof reducer(undefined, {
 78         type: ActionTypes.PROBE_UNKNOWN_ACTION()
 79       }) === ‘undefined‘
 80     ) {
 81       throw new Error(
 82         `Reducer "${key}" returned undefined when probed with a random type. ` +
 83           `Don‘t try to handle ${ActionTypes.INIT} or other actions in "redux/*" ` +
 84           `namespace. They are considered private. Instead, you must return the ` +
 85           `current state for any unknown actions, unless it is undefined, ` +
 86           `in which case you must return the initial state, regardless of the ` +
 87           `action type. The initial state may not be undefined, but can be null.`
 88       )
 89     }
 90   })
 91 }
 92 
 93 /**
 94  * Turns an object whose values are different reducer functions, into a single
 95  * reducer function. It will call every child reducer, and gather their results
 96  * into a single state object, whose keys correspond to the keys of the passed
 97  * reducer functions.
 98  *
 99  * @param {Object} reducers An object whose values correspond to different
100  * reducer functions that need to be combined into one. One handy way to obtain
101  * it is to use ES6 `import * as reducers` syntax. The reducers may never return
102  * undefined for any action. Instead, they should return their initial state
103  * if the state passed to them was undefined, and the current state for any
104  * unrecognized action.
105  *
106  * @returns {Function} A reducer function that invokes every reducer inside the
107  * passed object, and builds a state object with the same shape.
108  */
109 export default function combineReducers(reducers) {
110   const reducerKeys = Object.keys(reducers)
111   const finalReducers = {}
112   for (let i = 0; i < reducerKeys.length; i++) {
113     const key = reducerKeys[i]
114 
115     if (process.env.NODE_ENV !== ‘production‘) {
116       if (typeof reducers[key] === ‘undefined‘) {
117         warning(`No reducer provided for key "${key}"`)
118       }
119     }
120 
121     if (typeof reducers[key] === ‘function‘) {
122       finalReducers[key] = reducers[key]
123     }
124   }
125   const finalReducerKeys = Object.keys(finalReducers)
126 
127   // This is used to make sure we don‘t warn about the same
128   // keys multiple times.
129   let unexpectedKeyCache
130   if (process.env.NODE_ENV !== ‘production‘) {
131     unexpectedKeyCache = {}
132   }
133 
134   let shapeAssertionError
135   try {
136     assertReducerShape(finalReducers)
137   } catch (e) {
138     shapeAssertionError = e
139   }
140 
141   return function combination(state = {}, action) {
142     if (shapeAssertionError) {
143       throw shapeAssertionError
144     }
145 
146     if (process.env.NODE_ENV !== ‘production‘) {
147       const warningMessage = getUnexpectedStateShapeWarningMessage(
148         state,
149         finalReducers,
150         action,
151         unexpectedKeyCache
152       )
153       if (warningMessage) {
154         warning(warningMessage)
155       }
156     }
157 
158     let hasChanged = false
159     const nextState = {}
160     for (let i = 0; i < finalReducerKeys.length; i++) {
161       const key = finalReducerKeys[i]
162       const reducer = finalReducers[key]
163       const previousStateForKey = state[key]
164       const nextStateForKey = reducer(previousStateForKey, action)
165       if (typeof nextStateForKey === ‘undefined‘) {
166         const errorMessage = getUndefinedStateErrorMessage(key, action)
167         throw new Error(errorMessage)
168       }
169       nextState[key] = nextStateForKey
170       hasChanged = hasChanged || nextStateForKey !== previousStateForKey
171     }
172     hasChanged =
173       hasChanged || finalReducerKeys.length !== Object.keys(state).length
174     return hasChanged ? nextState : state
175   }
176 }

按照reducer工作的顺序分为: 初始化 state  →  验证并提取reducer的键名 →  使用当前state根据action生成新的state

初始化 state :  

assertReducerShape 方法: 64行代码   const initialState = reducer(undefined, { type: ActionTypes.INIT }) ,通过内置的 ActionTypes.INIT初始化state,  值为  undefined. 所以在编写reducer的时候,要传入默认值,否则state默认是 undefined, 当然了你可以在createStore的第二个参数赋值传入。
 
验证并提取reducer的键名:
combineReducers 方法: 109行 ~ 140行
export default function combineReducers(reducers) {
  const reducerKeys = Object.keys(reducers)
  const finalReducers = {}
  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
  if (process.env.NODE_ENV !== ‘production‘) {
    unexpectedKeyCache = {}
  }

  let shapeAssertionError
  try {
    assertReducerShape(finalReducers)
  } catch (e) {
    shapeAssertionError = e
  }
......

核心代码  const reducerKeys = Object.keys(reducers) 以及后面紧跟的 for语句, 抽取了reducers (  这个例子中的  eatSomething,  drinkSomething, readSomething

const reducers = combineReducers({
  eatSomething,
  drinkSomething,
  readSome
})

)中的键名 并根据键名验证了传入的reducer是否是 function, 最后将键名保存在 变量 finalReducerKeys 中, 对应的 reducers 保存在 变量  finalReducers 中;

 

使用当前state根据action生成新的state:

combineReducers 方法: 140行 ~ 最后

...... 
return function combination(state = {}, action) {
    if (shapeAssertionError) {
      throw shapeAssertionError
    }

    if (process.env.NODE_ENV !== ‘production‘) {
      const warningMessage = getUnexpectedStateShapeWarningMessage(
        state,
        finalReducers,
        action,
        unexpectedKeyCache
      )
      if (warningMessage) {
        warning(warningMessage)
      }
    }

    let hasChanged = false
    const nextState = {}
    for (let i = 0; i < finalReducerKeys.length; i++) {
      const key = finalReducerKeys[i]
      const reducer = finalReducers[key]
      const previousStateForKey = state[key]
      const nextStateForKey = reducer(previousStateForKey, action)
      if (typeof nextStateForKey === ‘undefined‘) {
        const errorMessage = getUndefinedStateErrorMessage(key, action)
        throw new Error(errorMessage)
      }
      nextState[key] = nextStateForKey
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey
    }
    hasChanged =
      hasChanged || finalReducerKeys.length !== Object.keys(state).length
    return hasChanged ? nextState : state
  }
}

主要代码也是 for 语句 ,根据上一步的  finalReducers 遍历 ,保存当前 state  const previousStateForKey = state[key] , 按照遍历得到的reducer和当前state计算下一个state  const nextStateForKey = reducer(previousStateForKey, action) , 这个返回的方法中有一个状态值 hasChanged ,他的作用是 这个   return hasChanged ? nextState : state ,根据值的改变,对比上一个和下一个state是否相同,同时这个值还取决于 传入reducers 长度是否有变化 ,进而判断是否返回新值。

到这里reducer的解读完毕!

 

上一篇:createStore解读

 
 

 

以上是关于createStore第一个参数reducer的主要内容,如果未能解决你的问题,请参考以下文章

无法在 Redux 中设置初始状态

在 Redux Reducer 中读取 Store 的初始状态

redux初识

尽管向 createStore() 提供了 initialState,为啥我得到“Reducer [...] 在初始化期间返回未定义”?

精选博客|redux中间件机制—源码解析

redux教程之源码解析createStore