正式学习React ----Redux源码分析

Posted 暗影侠客

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了正式学习React ----Redux源码分析相关的知识,希望对你有一定的参考价值。

今天看了下Redux的源码,竟然出奇的简单,好吧。简单翻译做下笔记:

喜欢的同学自己可以去github上看:点这里

createStore.js

 

  1 import isPlainObject from \'lodash/isPlainObject\'
  2 import $$observable from \'symbol-observable\'
  3 
  4 /**
  5  * These are private action types reserved by Redux.
  6  * For any unknown actions, you must return the current state.
  7  * If the current state is undefined, you must return the initial state.
  8  * Do not reference these action types directly in your code.
  9  */
 10 export var ActionTypes = {
 11   INIT: \'@@redux/INIT\'
 12 }
 13 
 14 /**
 15  * Creates a Redux store that holds the state tree.
 16  * The only way to change the data in the store is to call `dispatch()` on it.
 17  *
 18  * There should only be a single store in your app. To specify how different
 19  * parts of the state tree respond to actions, you may combine several reducers
 20  * into a single reducer function by using `combineReducers`.
 21  *
 22  * @param {Function} reducer A function that returns the next state tree, given
 23  * the current state tree and the action to handle.
 24  *
 25  * @param {any} [preloadedState] The initial state. You may optionally specify it
 26  * to hydrate the state from the server in universal apps, or to restore a
 27  * previously serialized user session.
 28  * If you use `combineReducers` to produce the root reducer function, this must be
 29  * an object with the same shape as `combineReducers` keys.
 30  *
 31  * @param {Function} [enhancer] The store enhancer. You may optionally specify it
 32  * to enhance the store with third-party capabilities such as middleware,
 33  * time travel, persistence, etc. The only store enhancer that ships with Redux
 34  * is `applyMiddleware()`.
 35  *
 36  * @returns {Store} A Redux store that lets you read the state, dispatch actions
 37  * and subscribe to changes.
 38  */
 39 export default function createStore(reducer, preloadedState, enhancer) {

//如果没有提供初始的state,提供了enhancer,就将初试state置为undefined

40 if (typeof preloadedState === \'function\' && typeof enhancer === \'undefined\') { 41 enhancer = preloadedState 42 preloadedState = undefined 43 } 44

//确保enhancer一定是函数 45 if (typeof enhancer !== \'undefined\') { 46 if (typeof enhancer !== \'function\') { 47 throw new Error(\'Expected the enhancer to be a function.\') 48 } 49 //这个会返回一个store,只不过是增强版的。 50 return enhancer(createStore)(reducer, preloadedState) 51 } 52
53 if (typeof reducer !== \'function\') { 54 throw new Error(\'Expected the reducer to be a function.\') 55 } 56

//初始化一些变量,用作闭包。 57 var currentReducer = reducer 58 var currentState = preloadedState //undefined || 传进来的初始值 59 var currentListeners = [] 60 var nextListeners = currentListeners 61 var isDispatching = false 62

//这个辅助函数是这样的,如果你没有调用dispacth,那么每次调用subscribe来添加监听器的都会被push到nextListenrs,他是currentListerns的一个副本。

//总的来说这个函数的意义我个人觉得就是保护了currentListeners不被随意污染.保证每次dispacth前状态不变。 63 function ensureCanMutateNextListeners() { 64 if (nextListeners === currentListeners) { 65 nextListeners = currentListeners.slice() 66 } 67 } 68 69 /** 70 * Reads the state tree managed by the store. 71 * 72 * @returns {any} The current state tree of your application. 73 */
//这个就是返回当前的state 74 function getState() { 75 return currentState 76 } 77 78 /** 79 * Adds a change listener. It will be called any time an action is dispatched, 80 * and some part of the state tree may potentially have changed. You may then 81 * call `getState()` to read the current state tree inside the callback. 82 * 83 * You may call `dispatch()` from a change listener, with the following 84 * caveats: 85 * 86 * 1. The subscriptions are snapshotted just before every `dispatch()` call. 87 * If you subscribe or unsubscribe while the listeners are being invoked, this 88 * will not have any effect on the `dispatch()` that is currently in progress. 89 * However, the next `dispatch()` call, whether nested or not, will use a more 90 * recent snapshot of the subscription list. 91 * 92 * 2. The listener should not expect to see all state changes, as the state 93 * might have been updated multiple times during a nested `dispatch()` before 94 * the listener is called. It is, however, guaranteed that all subscribers 95 * registered before the `dispatch()` started will be called with the latest 96 * state by the time it exits. 97 * 98 * @param {Function} listener A callback to be invoked on every dispatch. 99 * @returns {Function} A function to remove this change listener. 100 */

//简单点说就是 设置监听函数,每次dispatch(action)的时候,这些传进去的listenr们就会全部被调用 101 function subscribe(listener) { 102 if (typeof listener !== \'function\') { 103 throw new Error(\'Expected listener to be a function.\') 104 } 105 106 var isSubscribed = true 107 108 ensureCanMutateNextListeners() 109 nextListeners.push(listener) 110 111 return function unsubscribe() { 112 if (!isSubscribed) { 113 return 114 } 115 116 isSubscribed = false 117 118 ensureCanMutateNextListeners() 119 var index = nextListeners.indexOf(listener) 120 nextListeners.splice(index, 1) 121 } 122 } 123 124 /** 125 * Dispatches an action. It is the only way to trigger a state change. 126 * 127 * The `reducer` function, used to create the store, will be called with the 128 * current state tree and the given `action`. Its return value will 129 * be considered the **next** state of the tree, and the change listeners 130 * will be notified. 131 * 132 * The base implementation only supports plain object actions. If you want to 133 * dispatch a Promise, an Observable, a thunk, or something else, you need to 134 * wrap your store creating function into the corresponding middleware. For 135 * example, see the documentation for the `redux-thunk` package. Even the 136 * middleware will eventually dispatch plain object actions using this method. 137 * 138 * @param {Object} action A plain object representing “what changed”. It is 139 * a good idea to keep actions serializable so you can record and replay user 140 * sessions, or use the time travelling `redux-devtools`. An action must have 141 * a `type` property which may not be `undefined`. It is a good idea to use 142 * string constants for action types. 143 * 144 * @returns {Object} For convenience, the same action object you dispatched. 145 * 146 * Note that, if you use a custom middleware, it may wrap `dispatch()` to 147 * return something else (for example, a Promise you can await). 148 */


//通知store,我要更新state了。 149 function dispatch(action) { 150 if (!isPlainObject(action)) { 151 throw new Error( 152 \'Actions must be plain objects. \' + 153 \'Use custom middleware for async actions.\' 154 ) 155 } 156 157 if (typeof action.type === \'undefined\') { 158 throw new Error( 159 \'Actions may not have an undefined "type" property. \' + 160 \'Have you misspelled a constant?\' 161 ) 162 } 163 164 if (isDispatching) { 165 throw new Error(\'Reducers may not dispatch actions.\') 166 } 167 168 try { 169 isDispatching = true 170 currentState = currentReducer(currentState, action) 171 } finally { 172 isDispatching = false 173 } 174 175 var listeners = currentListeners = nextListeners 176 for (var i = 0; i < listeners.length; i++) { 177 var listener = listeners[i] 178 listener() 179 } 180 181 return action 182 } 183 184 /** 185 * Replaces the reducer currently used by the store to calculate the state. 186 * 187 * You might need this if your app implements code splitting and you want to 188 * load some of the reducers dynamically. You might also need this if you 189 * implement a hot reloading mechanism for Redux. 190 * 191 * @param {Function} nextReducer The reducer for the store to use instead. 192 * @returns {void} 193 */

//重置reducer,然后初始化state 194 function replaceReducer(nextReducer) { 195 if (typeof nextReducer !== \'function\') { 196 throw new Error(\'Expected the nextReducer to be a function.\') 197 } 198 199 currentReducer = nextReducer 200 dispatch({ type: ActionTypes.INIT }) 201 } 202 203 /** 204 * Interoperability point for observable/reactive libraries. 205 * @returns {observable} A minimal observable of state changes. 206 * For more information, see the observable proposal: 207 * https://github.com/zenparsing/es-observable 208 */

//暂时我还不知道这个有什么吊用,先不管好了。 209 function observable() { 210 var outerSubscribe = subscribe 211 return { 212 /** 213 * The minimal observable subscription method. 214 * @param {Object} observer Any object that can be used as an observer. 215 * The observer object should have a `next` method. 216 * @returns {subscription} An object with an `unsubscribe` method that can 217 * be used to unsubscribe the observable from the store, and prevent further 218 * emission of values from the observable. 219 */ 220 subscribe(observer) { 221 if (typeof observer !== \'object\') { 222 throw new TypeError(\'Expected the observer to be an object.\') 223 } 224 225 function observeState() { 226 if (observer.next) { 227 observer.next(getState()) 228 } 229 } 230 231 observeState() 232 var unsubscribe = outerSubscribe(observeState) 233 return { unsubscribe } 234 }, 235 236 [$$observable]() { 237 return this 238 } 239 } 240 } 241 242 // When a store is created, an "INIT" action is dispatched so that every 243 // reducer returns their initial state. This effectively populates 244 // the initial state tree.


//给currentState设定初始状态 245 dispatch({ type: ActionTypes.INIT }) 246 247 return { 248 dispatch, 249 subscribe, 250 getState, 251 replaceReducer, 252 [$$observable]: observable 253 } 254 }

关于 createStore,我们就关注它返回的对象,subscribe是订阅监听函数的,getState是返回state的,dispacth是发布消息的,更新state的。

剩下那2个就不管他算了。

 

combineReducers.js

  1 import { ActionTypes } from \'./createStore\'
  2 import isPlainObject from \'lodash/isPlainObject\'
  3 import warning from \'./utils/warning\'
  4 
  5 var NODE_ENV = typeof process !== \'undefined\' ? process.env.NODE_ENV : \'development\'
  6 
  7 function getUndefinedStateErrorMessage(key, action) {
  8   var actionType = action && action.type
  9   var actionName = actionType && `"${actionType.toString()}"` || \'an action\'
 10 
 11   return (
 12     `Given action ${actionName}, reducer "${key}" returned undefined. ` +
 13     `To ignore an action, you must explicitly return the previous state.`
 14   )
 15 }
 16 
 17 function getUnexpectedStateShapeWarningMessage(inputState, reducers, action, unexpectedKeyCache) {
 18   var reducerKeys = Object.keys(reducers)
 19   var argumentName = action && action.type === ActionTypes.INIT ?
 20     \'preloadedState argument passed to createStore\' :
 21     \'previous state received by the reducer\'
 22 
 23   if (reducerKeys.length === 0) {
 24     return (
 25       \'Store does not have a valid reducer. Make sure the argument passed \' +
 26       \'to combineReducers is an object whose values are reducers.\'
 27     )
 28   }
 29 
 30   if (!isPlainObject(inputState)) {
 31     return (
 32       `The ${argumentName} has unexpected type of "` +
 33       ({}).toString.call(inputState).match(/\\s([a-z|A-Z]+)/)[1] +
 34       `". Expected argument to be an object with the following ` +
 35       `keys: "${reducerKeys.join(\'", "\')}"`
 36     )
 37   }
 38 
 39   var unexpectedKeys = Object.keys(inputState).filter(key =>
 40     !reducers.hasOwnProperty(key) &&
 41     !unexpectedKeyCache[key]
 42   )
 43 
 44   unexpectedKeys.forEach(key => {
 45     unexpectedKeyCache[key] = true
 46   })
 47 
 48   if (unexpectedKeys.length > 0) {
 49     return (
 50       `Unexpected ${unexpectedKeys.length > 1 ? \'keys\' : \'key\'} ` +
 51       `"${unexpectedKeys.join(\'", "\')}" found in ${argumentName}. ` +
 52       `Expected to find one of the known reducer keys instead: ` +
 53       `"${reducerKeys.join(\'", "\')}". Unexpected keys will be ignored.`
 54     )
 55   }
 56 }
 57 

58 function assertReducerSanity(reducers) { 59 Object.keys(reducers).forEach(key => { 60 var reducer = reducers[key] 61 var initialState = reducer(undefined, { type: ActionTypes.INIT }) 62 63 if (typeof initialState === \'undefined\') { 64 throw new Error( 65 `Reducer "${key}" returned undefined during initialization. ` + 66 `If the state passed to the reducer is undefined, you must ` + 67 `explicitly return the initial state. The initial state may ` + 68 `not be undefined.` 69 ) 70 } 71 72 var type = \'@@redux/PROBE_UNKNOWN_ACTION_\' + Math.random().toString(36).substring(7).split(\'\').join(\'.\') 73 if (typeof reducer(undefined, { type }) === \'undefined\') { 74 throw new Error( 75 `Reducer "${key}" returned undefined when probed with a random type. ` + 76 `Don\'t try to handle ${ActionTypes.INIT} or other actions in "redux/*" ` + 77 `namespace. They are considered private. Instead, you must return the ` + 78 `current state for any unknown actions, unless it is undefined, ` + 79 `in which case you must return the initial state, regardless of the ` + 80 `action type. The initial state may not be undefined.` 81 ) 82 } 83 }) 84 } 85 86 /** 87 * Turns an object whose values are different reducer functions, into a single 88 * reducer function. It will call every child reducer, and gather their results 89 * into a single state object, whose keys correspond to the keys of the passed 90 * reducer functions. 91 * 92 * @param {Object} reducers An object whose values correspond to different 93 * reducer functions that need to be combined into one. One handy way to obtain 94 * it is to use ES6 `import * as reducers` syntax. The reducers may never return 95 * undefined for any action. Instead, they should return their initial state 96 * if the state passed to them was undefined, and the current state for any 97 * unrecognized action. 98 * 99 * @returns {Function} A reducer function that invokes every reducer inside the 100 * passed object, and builds a state object with the same shape. 101 */ 102 export default function combineReducers(reducers) { 103 var reducerKeys = Object.keys(reducers) 104 var finalReducers = {}

105 for (var i = 0; i < reducerKeys.length; i++) { 106 var key = reducerKeys[i] 107 108 if (NODE_ENV !== \'production\') {

//排除undefined.
109 if (typeof reducers[key] === \'undefined\') { 110 warning(`No reducer provided for key "${key}"`) 111 } 112 } 113

//排除不是func的 114 if (typeof reducers[key] === \'function\') { 115 finalReducers[key] = reducers[key] 116 } 117 } 118 var finalReducerKeys = Object.keys(finalReducers) 119 120 if (NODE_ENV !== \'production\') { 121 var unexpectedKeyCache = {} 122 } 123 124 var sanityError 125 try {
//这里会对每个子reducer的state进行检查。返回不能为undefined
126 assertReducerSanity(finalReducers) 127 } catch (e) { 128 sanityError = e 129 } 130

//我们最最关心的就是这个返回函数,其实我们如果已经堆这个函数有一定的了解,就知道这个函数其实就把子reducers全部在这个函数里执行一边,
//返回最后的state 131 return function combination(state = {}, action) { 132 if (sanityError) { 133 throw sanityError 134 } 135 136 if (NODE_ENV !== \'production\') { 137 var warningMessage = getUnexpectedStateShapeWarningMessage(state, finalReducers, action, unexpectedKeyCache) 138 if (warningMessage) { 139 warning(warningMessage) 140 } 141 } 142 143 var hasChanged = false 144 var nextState = {} 145 for (var i = 0; i < finalReducerKeys.length; i++) { 146 var key = finalReducerKeys[i] 147 var reducer = finalReducers[key] 148 var previousStateForKey = state[key] 149 var nextStateForKey = reducer(previousStateForKey, action) 150 if (typeof nextStateForKey === \'undefined\') { 151 var errorMessage = getUndefinedStateErrorMessage(key, action) 152 throw new Error(errorMessage) 153 } 154 nextState[key] = nextStateForKey 155 hasChanged = hasChanged || nextStateForKey !== previousStateForKey 156 } 157 return hasChanged ? nextState : state 158 } 159 }

compose.js

 1 /**
 2  * Composes single-argument functions from right to left. The rightmost
 3  * function can take multiple arguments as it provides the signature for
 4  * the resulting composite function.
 5  *
 6  * @param {...Function} funcs The functions to compose.
 7  * @returns {Function} A function obtained by composing the argument functions
 8  * from right to left. For example, compose(f, g, h) is identical to doing
 9  * (...args) => f(g(h(...args))).
10  */
11 
12 export default function compose(...funcs) {
13     
14      //funcs就是我们传入的中间件,fn1,fn2...
15      //如果什么都没有,就返回一个function(arg){return arg};当作默认的中间件
16   if (funcs.length === 0) {
17     return arg => arg
18   }
19 
20 
21      //排除所有中间件参数中不是function的
22   funcs = funcs.filter(func => typeof func === \'function\')
23 
24 
25       //如果只有一个中间件参数,就把这个唯一的中间件返回。
26   if (funcs.length === 1) {
27     return funcs[0]
28   }
29 
30      //如果中间件参数个数超过1个
31 
32      //取出最后一个中间件参数
33   const last = funcs[funcs.length - 1]
34 
35      //将funcs中最开头到倒数第二个的数组复制一份,即排除了最后一个中间件以上是关于正式学习React ----Redux源码分析的主要内容,如果未能解决你的问题,请参考以下文章

正式学习React( 三)

移动开发者如何更好地学习 React Native? | 技术头条

初步学习React Router 4.0

全新Wijmo5中文学习指南正式上线

React18正式版发布,未来发展趋势如何?

React v15.0 正式版发布