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 :
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 Reducer 中读取 Store 的初始状态
尽管向 createStore() 提供了 initialState,为啥我得到“Reducer [...] 在初始化期间返回未定义”?