zoukankan      html  css  js  c++  java
  • 正式学习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中最开头到倒数第二个的数组复制一份,即排除了最后一个中间件
    36   const rest = funcs.slice(0, -1)
    37 
    38 
    39   //返回(...args) => f(g(h(...args))).
    40   return (...args) => rest.reduceRight((composed, f) => f(composed), last(...args))
    41 }

    解释完compose.js,我们可以简单的理解为: compose就是用来将函数组合起来使用。

     假如:compose(f, g, h)
     分装后:(...args) => f(g(h(...args))).

    applyMiddleware.js

     1 import compose from './compose'
     2 
     3 /**
     4  * Creates a store enhancer that applies middleware to the dispatch method
     5  * of the Redux store. This is handy for a variety of tasks, such as expressing
     6  * asynchronous actions in a concise manner, or logging every action payload.
     7  *
     8  * See `redux-thunk` package as an example of the Redux middleware.
     9  *
    10  * Because middleware is potentially asynchronous, this should be the first
    11  * store enhancer in the composition chain.
    12  *
    13  * Note that each middleware will be given the `dispatch` and `getState` functions
    14  * as named arguments.
    15  *
    16  * @param {...Function} middlewares The middleware chain to be applied.
    17  * @returns {Function} A store enhancer applying the middleware.
    18  */
    19 export default function applyMiddleware(...middlewares) {

    //短短几行,很简单哟。
    //返回我们在creteStore里看到的enhancer
    20   return (createStore) => (reducer, preloadedState, enhancer) => {
    21     var store = createStore(reducer, preloadedState, enhancer)
    22     var dispatch = store.dispatch
    23     var chain = []
    24 
    25     var middlewareAPI = {
    26       getState: store.getState,
    27       dispatch: (action) => dispatch(action)
    28     }

    //将 当前store的getState和dispacth方法挂载到中间件里。
    29 chain = middlewares.map(middleware => middleware(middlewareAPI)) 30 dispatch = compose(...chain)(store.dispatch) 31 32 return { 33 ...store, 34 dispatch 35 } 36 } 37 }

     假如我们在代码里看到:

    const store =  applyMiddleware(promise, thunk, observable)(createStore)(reducer);
    亦或是

    const store = createStore(
    reducer,state,applyMiddleware(promise, thunk, observable)
    );

    我们根据上面的代码走一遍,看看是怎么个执行过程。

    1: applyMiddleware(promise, thunk, observable) 会返回enhancer.

    2:
    enhancer(createStore) 返回一个将中间件参数都闭包进来的 createStore函数,此时我们叫他加强版!我给他标注是红色;

    3:
    createStore(reducer) 然后就是上面代码的21-34行。 最后我们关心一下这个返回的store里的dispatch,其实是加强版的,因为里面是执行中间件的!!但是中间件里面到底干了什么,
    我们目前不得而知,总之记住,dispacth现在牛逼了就行。比如可以处理异步啦等等。
    bindActionCreators.js

     1 function bindActionCreator(actionCreator, dispatch) {
     2   return (...args) => dispatch(actionCreator(...args))
     3 }
     4 
     5 /**
     6  * Turns an object whose values are action creators, into an object with the
     7  * same keys, but with every function wrapped into a `dispatch` call so they
     8  * may be invoked directly. This is just a convenience method, as you can call
     9  * `store.dispatch(MyActionCreators.doSomething())` yourself just fine.
    10  *
    11  * For convenience, you can also pass a single function as the first argument,
    12  * and get a function in return.
    13  *
    14  * @param {Function|Object} actionCreators An object whose values are action
    15  * creator functions. One handy way to obtain it is to use ES6 `import * as`
    16  * syntax. You may also pass a single function.
    17  *
    18  * @param {Function} dispatch The `dispatch` function available on your Redux
    19  * store.
    20  *
    21  * @returns {Function|Object} The object mimicking the original object, but with
    22  * every action creator wrapped into the `dispatch` call. If you passed a
    23  * function as `actionCreators`, the return value will also be a single
    24  * function.
    25  */
    26 export default function bindActionCreators(actionCreators, dispatch) {
    27   if (typeof actionCreators === 'function') {
    28     return bindActionCreator(actionCreators, dispatch)
    29   }
    30 
    31   if (typeof actionCreators !== 'object' || actionCreators === null) {
    32     throw new Error(
    33       `bindActionCreators expected an object or a function, instead received ${actionCreators === null ? 'null' : typeof actionCreators}. ` +
    34       `Did you write "import ActionCreators from" instead of "import * as ActionCreators from"?`
    35     )
    36   }
    37 
    38   var keys = Object.keys(actionCreators)
    39   var boundActionCreators = {}
    40   for (var i = 0; i < keys.length; i++) {
    41     var key = keys[i]
    42     var actionCreator = actionCreators[key]
    43     if (typeof actionCreator === 'function') {
    44       boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
    45     }
    46   }
    47   return boundActionCreators
    48 }

    这个东西唯一的好处是往下传actionCreator和dispatch的时候,少些代码,无形中在props里加了内容!!!!

    具体应用场景,大家自行谷歌。因为我也才学这个第8天,这函数我自己都没用过····哈哈。不过以后肯定要用了!!!

    代码十分简单,我就不分析了。

    这里有一篇园内的朋友写的它的应用,凑合看吧:点这里



  • 相关阅读:
    Codeforces 177G2 Fibonacci Strings KMP 矩阵
    Codeforces Gym100187C Very Spacious Office 贪心 堆
    Codeforces 980F Cactus to Tree 仙人掌 Tarjan 树形dp 单调队列
    AtCoder SoundHound Inc. Programming Contest 2018 E + Graph (soundhound2018_summer_qual_e)
    BZOJ3622 已经没有什么好害怕的了 动态规划 容斥原理 组合数学
    NOIP2016提高组Day1T2 天天爱跑步 树链剖分 LCA 倍增 差分
    Codeforces 555C Case of Chocolate 其他
    NOIP2017提高组Day2T3 列队 洛谷P3960 线段树
    NOIP2017提高组Day2T2 宝藏 洛谷P3959 状压dp
    NOIP2017提高组Day1T3 逛公园 洛谷P3953 Tarjan 强连通缩点 SPFA 动态规划 最短路 拓扑序
  • 原文地址:https://www.cnblogs.com/huenchao/p/6092266.html
Copyright © 2011-2022 走看看