├── LICENSE ├── README.md ├── dist ├── avalon.js └── mmDux.js ├── doc ├── combine-reducers.html ├── dispatch-action.html ├── get-store.html └── store.png ├── package.json ├── src ├── applyMiddleware.js ├── bindActionCreator.js ├── combineReducers.js ├── compose.js ├── createStore.js └── index.js └── webpack.config.js /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 司徒正美 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | mmDux 2 | ======= 3 | 4 | mmDux是redux的avalon版本 5 | 6 | 只是将里面store的observable方法去掉, 里面的辅助函数改成avalon的工具方法 7 | 8 | mmDux是一种可预知的状态容器 9 | 10 | ##Store 11 | 12 | store 是一个单一对象: 13 | 14 | 1. 管理应用的 state 15 | 2. 通过 store.getState() 可以获取 state 16 | 3. 通过 store.dispatch(action) 来触发 state 更新 17 | 4. 通过 store.subscribe(listener) 来注册 state 变化监听器 18 | 5. 通过 createStore(reducer, [initialState]) 创建 19 | 20 | ``` 21 | var store = mmDux.createStore(function(){ 22 | return {} 23 | }) 24 | console.log(store) 25 | 26 | ``` 27 | ![](./doc/store.png) 28 | 29 | ##ActionCreator 30 | 31 | action是一个普通的对象,用来描述应用程序中发生的某件事情, 32 | 它是应用程序中可以用来描述数据变化的唯一角色。 33 | 34 | actionCreator 是一个函数,用来创建一个action: 35 | ```javascript 36 | var actionCreator = function() { 37 | return { 38 | type: 'AN_ACTION' //注意type值必须全部大写 39 | } 40 | } 41 | ``` 42 | 43 | ##Reducers 44 | 45 | Actions用来告诉我们应用程序中有某件事情发生了,但是并不会告诉store这些改变改如何响应, 46 | 也不会改变store。这就是Reducers的工作。 47 | ```javascript 48 | var reducer = function () { 49 | console.log('Reducer was called with args', arguments) 50 | } 51 | 52 | var store = createStore(reducer) 53 | ``` 54 | 55 | 这里的reducer也就是根reducer,但我们可以有多个reducer,他们分别只处理一部分的state。 56 | 然后通过combineReducers组成根reducer用来创建一个store, 比如: 57 | ```javascript 58 | var userReducer = function (state = {}, action) { 59 | // etc. 60 | } 61 | var itemsReducer = function (state = [], action) { 62 | // etc. 63 | } 64 | var reducer = combineReducers({ 65 | user: userReducer, 66 | items: itemsReducer 67 | }) 68 | ``` 69 | 70 | mmDux 的核心思想之一就是,它不直接修改整个应用的状态树, 71 | 而是将状态树的每一部分进行拷贝并修改拷贝后的部分,然后将这些部分重新组合成一颗新的状态树。 72 | 73 | 也就是说,子 reducers 会把他们创建的副本传回给根 reducer, 74 | 而根 reducer 会把这些副本组合起来形成一颗新的状态树。最后根 reducer 将新的状态树传回给 store, 75 | store 再将新的状态树设为最终的状态。 76 | 77 | Middleware 78 | middleware 其实就是高阶函数,作用于 dispatch 返回一个新的 dispatch(附加了该中间件功能)。 79 | 可以形式化为:newDispatch = middleware1(middleware2(...(dispatch)...))。 80 | 81 | ``` 82 | //redux-thunk 83 | function createThunkMiddleware(extraArgument) { 84 | return function({ dispatch, getState }) { 85 | return function(next){ 86 | return function(action){ 87 | if (typeof action === 'function') { 88 | return action(dispatch, getState, extraArgument); 89 | } 90 | 91 | return next(action); 92 | } 93 | } 94 | } 95 | 96 | }; 97 | } 98 | 99 | var thunk = createThunkMiddleware(); 100 | thunk.withExtraArgument = createThunkMiddleware; 101 | 102 | module.exports = thunk 103 | 104 | ``` 105 | 106 | -------------------------------------------------------------------------------- /dist/mmDux.js: -------------------------------------------------------------------------------- 1 | (function webpackUniversalModuleDefinition(root, factory) { 2 | if(typeof exports === 'object' && typeof module === 'object') 3 | module.exports = factory(); 4 | else if(typeof define === 'function' && define.amd) 5 | define([], factory); 6 | else if(typeof exports === 'object') 7 | exports["mmDux"] = factory(); 8 | else 9 | root["mmDux"] = factory(); 10 | })(this, function() { 11 | return /******/ (function(modules) { // webpackBootstrap 12 | /******/ // The module cache 13 | /******/ var installedModules = {}; 14 | 15 | /******/ // The require function 16 | /******/ function __webpack_require__(moduleId) { 17 | 18 | /******/ // Check if module is in cache 19 | /******/ if(installedModules[moduleId]) 20 | /******/ return installedModules[moduleId].exports; 21 | 22 | /******/ // Create a new module (and put it into the cache) 23 | /******/ var module = installedModules[moduleId] = { 24 | /******/ exports: {}, 25 | /******/ id: moduleId, 26 | /******/ loaded: false 27 | /******/ }; 28 | 29 | /******/ // Execute the module function 30 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 31 | 32 | /******/ // Flag the module as loaded 33 | /******/ module.loaded = true; 34 | 35 | /******/ // Return the exports of the module 36 | /******/ return module.exports; 37 | /******/ } 38 | 39 | 40 | /******/ // expose the modules object (__webpack_modules__) 41 | /******/ __webpack_require__.m = modules; 42 | 43 | /******/ // expose the module cache 44 | /******/ __webpack_require__.c = installedModules; 45 | 46 | /******/ // __webpack_public_path__ 47 | /******/ __webpack_require__.p = ""; 48 | 49 | /******/ // Load entry module and return exports 50 | /******/ return __webpack_require__(0); 51 | /******/ }) 52 | /************************************************************************/ 53 | /******/ ([ 54 | /* 0 */ 55 | /***/ function(module, exports, __webpack_require__) { 56 | 57 | if(typeof avalon !== 'function'){ 58 | throw 'mmDux need avalon2' 59 | } 60 | var createStore = __webpack_require__(1) 61 | var combineReducers = __webpack_require__(2) 62 | var bindActionCreator = __webpack_require__(3) 63 | var applyMiddleware = __webpack_require__(4) 64 | var compose = __webpack_require__(5) 65 | 66 | module.exports = { 67 | createStore: createStore, 68 | combineReducers: combineReducers, 69 | bindActionCreator: bindActionCreator, 70 | applyMiddleware: applyMiddleware, 71 | compose: compose 72 | } 73 | 74 | /***/ }, 75 | /* 1 */ 76 | /***/ function(module, exports) { 77 | 78 | var ActionTypes = { 79 | INIT: '@@redux/INIT' 80 | } 81 | createStore.ActionTypes = ActionTypes 82 | /* 83 | * 84 | redux.createStore(reducer, initialState) 传入了reducer、initialState, 85 | 并返回一个store对象。 86 | store对象对外暴露了dispatch、getState、subscribe方法 87 | store对象通过getState() 获取内部状态 88 | initialState为 store 的初始状态,如果不传则为undefined 89 | store对象通过reducer来修改内部状态 90 | store对象创建的时候,内部会主动调用dispatch({ type: ActionTypes.INIT }); 91 | 来对内部状态进行初始化。 92 | 通过断点或者日志打印就可以看到,store对象创建的同时,reducer就会被调用进行初始化。 93 | * 94 | */ 95 | function createStore(reducer, preloadedState, enhancer) { 96 | if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') { 97 | enhancer = preloadedState 98 | preloadedState = undefined 99 | } 100 | 101 | if (typeof enhancer !== 'undefined') { 102 | if (typeof enhancer !== 'function') { 103 | throw new Error('Expected the enhancer to be a function.') 104 | } 105 | 106 | return enhancer(createStore)(reducer, preloadedState) 107 | } 108 | 109 | if (typeof reducer !== 'function') { 110 | throw new Error('Expected the reducer to be a function.') 111 | } 112 | 113 | var currentReducer = reducer 114 | var currentState = preloadedState 115 | var currentListeners = [] 116 | var nextListeners = currentListeners 117 | var isDispatching = false 118 | 119 | function ensureCanMutateNextListeners() { 120 | if (nextListeners === currentListeners) { 121 | nextListeners = currentListeners.slice()//复制一份 122 | } 123 | } 124 | 125 | 126 | function getState() { 127 | return currentState 128 | } 129 | 130 | 131 | function subscribe(listener) { 132 | if (typeof listener !== 'function') { 133 | throw new Error('Expected listener to be a function.') 134 | } 135 | 136 | var isSubscribed = true 137 | 138 | ensureCanMutateNextListeners() 139 | nextListeners.push(listener) 140 | 141 | return function unsubscribe() { 142 | if (!isSubscribed) { 143 | return 144 | } 145 | 146 | isSubscribed = false 147 | 148 | ensureCanMutateNextListeners() 149 | var index = nextListeners.indexOf(listener) 150 | nextListeners.splice(index, 1) 151 | } 152 | } 153 | 154 | 155 | function dispatch(action) { 156 | if (!avalon.isPlainObject(action)) { 157 | avalon.error('Actions 必须是一个朴素的JS对象') 158 | } 159 | 160 | if (typeof action.type !== 'string') { 161 | avalon.error('action必须定义type属性') 162 | } 163 | 164 | 165 | 166 | if (isDispatching) { 167 | avalon.error('Reducers may not dispatch actions.') 168 | } 169 | 170 | try { 171 | isDispatching = true 172 | currentState = currentReducer(currentState, action) 173 | } finally { 174 | isDispatching = false 175 | } 176 | 177 | var listeners = currentListeners = nextListeners 178 | for (var i = 0; i < listeners.length; i++) { 179 | listeners[i]() 180 | } 181 | 182 | return action 183 | } 184 | 185 | /** 186 | * Replaces the reducer currently used by the store to calculate the state. 187 | * 188 | * You might need this if your app implements code splitting and you want to 189 | * load some of the reducers dynamically. You might also need this if you 190 | * implement a hot reloading mechanism for Redux. 191 | * 192 | * @param {Function} nextReducer The reducer for the store to use instead. 193 | * @returns {void} 194 | */ 195 | function replaceReducer(nextReducer) { 196 | if (typeof nextReducer !== 'function') { 197 | throw new Error('Expected the nextReducer to be a function.') 198 | } 199 | 200 | currentReducer = nextReducer 201 | dispatch({type: ActionTypes.INIT}) 202 | } 203 | 204 | /** 205 | * Interoperability point for observable/reactive libraries. 206 | * @returns {observable} A minimal observable of state changes. 207 | * For more information, see the observable proposal: 208 | * https://github.com/zenparsing/es-observable 209 | */ 210 | 211 | 212 | // When a store is created, an "INIT" action is dispatched so that every 213 | // reducer returns their initial state. This effectively populates 214 | // the initial state tree. 215 | dispatch({type: ActionTypes.INIT}) 216 | 217 | return { 218 | dispatch: dispatch, 219 | subscribe: subscribe, 220 | getState: getState, 221 | replaceReducer: replaceReducer 222 | // [$$observable]: observable 223 | } 224 | } 225 | 226 | module.exports = createStore 227 | 228 | /***/ }, 229 | /* 2 */ 230 | /***/ function(module, exports, __webpack_require__) { 231 | 232 | var createStore = __webpack_require__(1) 233 | /* 234 | * 235 | * 将一个reducer map转换为一个reducer。方便对复杂的reducer进行功能拆分。 236 | */ 237 | 238 | function getUndefinedStateErrorMessage(key, action) { 239 | var actionType = action && action.type 240 | var actionName = actionType && '"' + actionType.toString() + '"' || 'an action' 241 | 242 | return 'Given action ' + actionName + ', reducer "' + key + '" returned undefined. ' + 'To ignore an action, you must explicitly return the previous state.' 243 | } 244 | 245 | function getUnexpectedStateShapeWarningMessage(inputState, reducers, action) { 246 | var reducerKeys = Object.keys(reducers) 247 | var argumentName = action && action.type === createStore.ActionTypes.INIT ? 248 | 'initialState argument passed to createStore' : 249 | 'previous state received by the reducer' 250 | 251 | if (reducerKeys.length === 0) { 252 | return 'Store does not have a valid reducer. Make sure the argument passed ' + 'to combineReducers is an object whose values are reducers.' 253 | } 254 | 255 | if (!avalon.isPlainObject(inputState)) { 256 | return 'The ' + argumentName + ' has unexpected type of "' + {}.toString.call(inputState).match(/\s([a-z|A-Z]+)/)[1] + '". Expected argument to be an object with the following ' + ('keys: "' + reducerKeys.join('", "') + '"') 257 | } 258 | 259 | var unexpectedKeys = Object.keys(inputState).filter(function (key) { 260 | return !reducers.hasOwnProperty(key) 261 | }) 262 | 263 | if (unexpectedKeys.length > 0) { 264 | return 'Unexpected ' + (unexpectedKeys.length > 1 ? 'keys' : 'key') + 265 | ' ' + ('"' + unexpectedKeys.join('", "') + 266 | '" found in ' + argumentName + '. ') + 'Expected to find one of the known reducer keys instead: ' + ('"' + reducerKeys.join('", "') + '". Unexpected keys will be ignored.') 267 | } 268 | } 269 | 270 | function assertReducerSanity(reducers) { 271 | Object.keys(reducers).forEach(function (key) { 272 | var reducer = reducers[key] 273 | var initialState = reducer(undefined, {type: createStore.ActionTypes.INIT}) 274 | 275 | if (typeof initialState === 'undefined') { 276 | throw new Error('Reducer "' + 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.') 277 | } 278 | 279 | var type = '@@redux/PROBE_UNKNOWN_ACTION_' + Math.random().toString(36).substring(7).split('').join('.') 280 | if (typeof reducer(undefined, {type: type}) === 'undefined') { 281 | throw new Error('Reducer "' + key + '" returned undefined when probed with a random type. ' + ('Don\'t try to handle ' + createStore.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.') 282 | } 283 | }) 284 | } 285 | 286 | 287 | function combineReducers(reducers) { 288 | var reducerKeys = Object.keys(reducers) 289 | var finalReducers = {}, finalReducerKeys = [] 290 | for (var i = 0; i < reducerKeys.length; i++) { 291 | var key = reducerKeys[i] 292 | if (typeof reducers[key] === 'function') { 293 | finalReducers[key] = reducers[key] 294 | finalReducerKeys.push(key) 295 | } 296 | } 297 | 298 | var sanityError 299 | try { 300 | assertReducerSanity(finalReducers) 301 | } catch (e) { 302 | sanityError = e 303 | } 304 | 305 | return function combination(state, action) { 306 | state = avalon.isObject(state) ? state : {} 307 | 308 | if (sanityError) { 309 | throw sanityError 310 | } 311 | 312 | if (true) { 313 | var warningMessage = getUnexpectedStateShapeWarningMessage(state, finalReducers, action) 314 | if (warningMessage) { 315 | avalon.warn(warningMessage) 316 | } 317 | } 318 | 319 | var hasChanged = false 320 | var nextState = {} 321 | for (var i = 0; i < finalReducerKeys.length; i++) { 322 | var key = finalReducerKeys[i] 323 | var reducer = finalReducers[key] 324 | var previousStateForKey = state[key] 325 | var nextStateForKey = reducer(previousStateForKey, action) 326 | if (typeof nextStateForKey === 'undefined') { 327 | var errorMessage = getUndefinedStateErrorMessage(key, action) 328 | throw new Error(errorMessage) 329 | } 330 | nextState[key] = nextStateForKey 331 | hasChanged = hasChanged || nextStateForKey !== previousStateForKey 332 | } 333 | return hasChanged ? nextState : state 334 | } 335 | } 336 | 337 | module.exports = combineReducers 338 | 339 | /***/ }, 340 | /* 3 */ 341 | /***/ function(module, exports) { 342 | 343 | function bindActionCreator(actionCreator, dispatch) { 344 | return function () { 345 | return dispatch(actionCreator.apply(undefined, arguments)) 346 | } 347 | } 348 | /* 349 | 用于描述action的dispatch的逻辑。 350 | 351 | action的重用 352 | 数据的预处理 353 | action的特殊处理逻辑 354 | 355 | */ 356 | 357 | function bindActionCreators(actionCreators, dispatch) { 358 | if (typeof actionCreators === 'function') { 359 | return bindActionCreator(actionCreators, dispatch) 360 | } 361 | 362 | if (!avalon.isObject(actionCreators)) { 363 | avalon.error('bindActionCreators的第一个参数必须是纯对象或函数') 364 | } 365 | 366 | var keys = Object.keys(actionCreators) 367 | var boundActionCreators = {} 368 | for (var i = 0; i < keys.length; i++) { 369 | var key = keys[i] 370 | var actionCreator = actionCreators[key] 371 | if (typeof actionCreator === 'function') { 372 | boundActionCreators[key] = bindActionCreator(actionCreator, dispatch) 373 | } 374 | } 375 | return boundActionCreators 376 | } 377 | 378 | module.exports = bindActionCreators 379 | 380 | /***/ }, 381 | /* 4 */ 382 | /***/ function(module, exports, __webpack_require__) { 383 | 384 | var compose = __webpack_require__(5) 385 | function applyMiddleware() { 386 | var middlewares = avalon.slice(arguments) 387 | 388 | 389 | return function (createStore) { 390 | return function (reducer, initialState, enhancer) { 391 | var store = createStore(reducer, initialState, enhancer) 392 | var _dispatch = store.dispatch 393 | var chain = [] 394 | 395 | var middlewareAPI = { 396 | getState: store.getState, 397 | dispatch: function (action) { 398 | return _dispatch(action) 399 | } 400 | } 401 | chain = middlewares.map(function (middleware) { 402 | return middleware(middlewareAPI) 403 | }) 404 | _dispatch = compose.apply(0, chain)(store.dispatch) 405 | 406 | return avalon.mix({}, store, { 407 | dispatch: _dispatch 408 | }) 409 | } 410 | } 411 | } 412 | 413 | module.exports = applyMiddleware 414 | 415 | /***/ }, 416 | /* 5 */ 417 | /***/ function(module, exports) { 418 | 419 | module.exports = function compose() { 420 | var fns = avalon.slice(arguments) 421 | 422 | 423 | if (fns.length === 0) { 424 | return function (arg) { 425 | return arg 426 | } 427 | } else { 428 | var _ret = function () { 429 | var last = fns[fns.length - 1] 430 | var rest = fns.slice(0, -1) 431 | return { 432 | v: function () { 433 | return reduceRight(rest, function (composed, f) { 434 | return f(composed) 435 | }, last.apply(undefined, arguments)) 436 | } 437 | } 438 | }() 439 | 440 | if (typeof _ret === "object") 441 | return _ret.v 442 | } 443 | } 444 | 445 | function reduceRight(ary, callback /*, initialValue*/) { 446 | if (ary.reduceRight) { 447 | return ary.reduceRight.apply(ary, avalon.slice(arguments,1)) 448 | } 449 | if ('function' !== typeof callback) { 450 | throw new TypeError(callback + ' is not a function') 451 | } 452 | var t = Object(ary), len = t.length >>> 0, k = len - 1, value 453 | if (arguments.length >= 3) { 454 | value = arguments[2] 455 | } else { 456 | while (k >= 0 && !(k in t)) { 457 | k-- 458 | } 459 | if (k < 0) { 460 | throw new TypeError('Reduce of empty array with no initial value') 461 | } 462 | value = t[k--] 463 | } 464 | for (; k >= 0; k--) { 465 | if (k in t) { 466 | value = callback(value, t[k], k, t) 467 | } 468 | } 469 | return value 470 | } 471 | 472 | 473 | /***/ } 474 | /******/ ]) 475 | }); 476 | ; -------------------------------------------------------------------------------- /doc/combine-reducers.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | TODO supply a title 5 | 6 | 7 | 8 | 9 | 127 | 128 | 129 |
mmDux.createStore与store.getState的使用
130 | 131 | 132 | -------------------------------------------------------------------------------- /doc/dispatch-action.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | TODO supply a title 5 | 6 | 7 | 8 | 9 | 104 | 105 | 106 |
mmDux.createStore与store.getState的使用
107 | 108 | 109 | -------------------------------------------------------------------------------- /doc/get-store.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | TODO supply a title 5 | 6 | 7 | 8 | 9 | 53 | 54 | 55 |
mmDux.createStore与store.getState的使用
56 | 57 | 58 | -------------------------------------------------------------------------------- /doc/store.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubyLouvre/mmDux/6566dc1c2127f625090d520f010e08ff734a4891/doc/store.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mmDux", 3 | "license": "MIT", 4 | "repository": { 5 | "type": "git", 6 | "url": "https://github.com/RubyLouvre/mmDux.git" 7 | }, 8 | "version": "1.0.0", 9 | "description": "avalon的redux", 10 | "main": "dist/index.js", 11 | "keywords": [ 12 | "javascript", 13 | "redux", 14 | "avalon" 15 | ], 16 | "dependencies": { 17 | "avalon2": "^2.1.0", 18 | "webpack": "^1.13.1" 19 | }, 20 | "devDependencies": { 21 | "avalon2": "^2.1.0" 22 | }, 23 | "bugs": { 24 | "url": "https://github.com/RubyLouvre/mmDux/issues" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/applyMiddleware.js: -------------------------------------------------------------------------------- 1 | var compose = require('./compose') 2 | function applyMiddleware() { 3 | var middlewares = avalon.slice(arguments) 4 | 5 | 6 | return function (createStore) { 7 | return function (reducer, initialState, enhancer) { 8 | var store = createStore(reducer, initialState, enhancer) 9 | var _dispatch = store.dispatch 10 | var chain = [] 11 | 12 | var middlewareAPI = { 13 | getState: store.getState, 14 | dispatch: function (action) { 15 | return _dispatch(action) 16 | } 17 | } 18 | chain = middlewares.map(function (middleware) { 19 | return middleware(middlewareAPI) 20 | }) 21 | _dispatch = compose.apply(0, chain)(store.dispatch) 22 | 23 | return avalon.mix({}, store, { 24 | dispatch: _dispatch 25 | }) 26 | } 27 | } 28 | } 29 | 30 | module.exports = applyMiddleware -------------------------------------------------------------------------------- /src/bindActionCreator.js: -------------------------------------------------------------------------------- 1 | function bindActionCreator(actionCreator, dispatch) { 2 | return function () { 3 | return dispatch(actionCreator.apply(undefined, arguments)) 4 | } 5 | } 6 | /* 7 | 用于描述action的dispatch的逻辑。 8 | 9 | action的重用 10 | 数据的预处理 11 | action的特殊处理逻辑 12 | 13 | */ 14 | 15 | function bindActionCreators(actionCreators, dispatch) { 16 | if (typeof actionCreators === 'function') { 17 | return bindActionCreator(actionCreators, dispatch) 18 | } 19 | 20 | if (!avalon.isObject(actionCreators)) { 21 | avalon.error('bindActionCreators的第一个参数必须是纯对象或函数') 22 | } 23 | 24 | var keys = Object.keys(actionCreators) 25 | var boundActionCreators = {} 26 | for (var i = 0; i < keys.length; i++) { 27 | var key = keys[i] 28 | var actionCreator = actionCreators[key] 29 | if (typeof actionCreator === 'function') { 30 | boundActionCreators[key] = bindActionCreator(actionCreator, dispatch) 31 | } 32 | } 33 | return boundActionCreators 34 | } 35 | 36 | module.exports = bindActionCreators -------------------------------------------------------------------------------- /src/combineReducers.js: -------------------------------------------------------------------------------- 1 | var createStore = require('./createStore') 2 | /* 3 | * 4 | * 将一个reducer map转换为一个reducer。方便对复杂的reducer进行功能拆分。 5 | */ 6 | 7 | function getUndefinedStateErrorMessage(key, action) { 8 | var actionType = action && action.type 9 | var actionName = actionType && '"' + actionType.toString() + '"' || 'an action' 10 | 11 | return 'Given action ' + actionName + ', reducer "' + key + '" returned undefined. ' + 'To ignore an action, you must explicitly return the previous state.' 12 | } 13 | 14 | function getUnexpectedStateShapeWarningMessage(inputState, reducers, action) { 15 | var reducerKeys = Object.keys(reducers) 16 | var argumentName = action && action.type === createStore.ActionTypes.INIT ? 17 | 'initialState argument passed to createStore' : 18 | 'previous state received by the reducer' 19 | 20 | if (reducerKeys.length === 0) { 21 | return 'Store does not have a valid reducer. Make sure the argument passed ' + 'to combineReducers is an object whose values are reducers.' 22 | } 23 | 24 | if (!avalon.isPlainObject(inputState)) { 25 | return 'The ' + argumentName + ' has unexpected type of "' + {}.toString.call(inputState).match(/\s([a-z|A-Z]+)/)[1] + '". Expected argument to be an object with the following ' + ('keys: "' + reducerKeys.join('", "') + '"') 26 | } 27 | 28 | var unexpectedKeys = Object.keys(inputState).filter(function (key) { 29 | return !reducers.hasOwnProperty(key) 30 | }) 31 | 32 | if (unexpectedKeys.length > 0) { 33 | return 'Unexpected ' + (unexpectedKeys.length > 1 ? 'keys' : 'key') + 34 | ' ' + ('"' + unexpectedKeys.join('", "') + 35 | '" found in ' + argumentName + '. ') + 'Expected to find one of the known reducer keys instead: ' + ('"' + reducerKeys.join('", "') + '". Unexpected keys will be ignored.') 36 | } 37 | } 38 | 39 | function assertReducerSanity(reducers) { 40 | Object.keys(reducers).forEach(function (key) { 41 | var reducer = reducers[key] 42 | var initialState = reducer(undefined, {type: createStore.ActionTypes.INIT}) 43 | 44 | if (typeof initialState === 'undefined') { 45 | throw new Error('Reducer "' + 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.') 46 | } 47 | 48 | var type = '@@redux/PROBE_UNKNOWN_ACTION_' + Math.random().toString(36).substring(7).split('').join('.') 49 | if (typeof reducer(undefined, {type: type}) === 'undefined') { 50 | throw new Error('Reducer "' + key + '" returned undefined when probed with a random type. ' + ('Don\'t try to handle ' + createStore.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.') 51 | } 52 | }) 53 | } 54 | 55 | 56 | function combineReducers(reducers) { 57 | var reducerKeys = Object.keys(reducers) 58 | var finalReducers = {}, finalReducerKeys = [] 59 | for (var i = 0; i < reducerKeys.length; i++) { 60 | var key = reducerKeys[i] 61 | if (typeof reducers[key] === 'function') { 62 | finalReducers[key] = reducers[key] 63 | finalReducerKeys.push(key) 64 | } 65 | } 66 | 67 | var sanityError 68 | try { 69 | assertReducerSanity(finalReducers) 70 | } catch (e) { 71 | sanityError = e 72 | } 73 | 74 | return function combination(state, action) { 75 | state = avalon.isObject(state) ? state : {} 76 | 77 | if (sanityError) { 78 | throw sanityError 79 | } 80 | 81 | if (true) { 82 | var warningMessage = getUnexpectedStateShapeWarningMessage(state, finalReducers, action) 83 | if (warningMessage) { 84 | avalon.warn(warningMessage) 85 | } 86 | } 87 | 88 | var hasChanged = false 89 | var nextState = {} 90 | for (var i = 0; i < finalReducerKeys.length; i++) { 91 | var key = finalReducerKeys[i] 92 | var reducer = finalReducers[key] 93 | var previousStateForKey = state[key] 94 | var nextStateForKey = reducer(previousStateForKey, action) 95 | if (typeof nextStateForKey === 'undefined') { 96 | var errorMessage = getUndefinedStateErrorMessage(key, action) 97 | throw new Error(errorMessage) 98 | } 99 | nextState[key] = nextStateForKey 100 | hasChanged = hasChanged || nextStateForKey !== previousStateForKey 101 | } 102 | return hasChanged ? nextState : state 103 | } 104 | } 105 | 106 | module.exports = combineReducers -------------------------------------------------------------------------------- /src/compose.js: -------------------------------------------------------------------------------- 1 | module.exports = function compose() { 2 | var fns = avalon.slice(arguments) 3 | 4 | 5 | if (fns.length === 0) { 6 | return function (arg) { 7 | return arg 8 | } 9 | } else { 10 | var _ret = function () { 11 | var last = fns[fns.length - 1] 12 | var rest = fns.slice(0, -1) 13 | return { 14 | v: function () { 15 | return reduceRight(rest, function (composed, f) { 16 | return f(composed) 17 | }, last.apply(undefined, arguments)) 18 | } 19 | } 20 | }() 21 | 22 | if (typeof _ret === "object") 23 | return _ret.v 24 | } 25 | } 26 | 27 | function reduceRight(ary, callback /*, initialValue*/) { 28 | if (ary.reduceRight) { 29 | return ary.reduceRight.apply(ary, avalon.slice(arguments,1)) 30 | } 31 | if ('function' !== typeof callback) { 32 | throw new TypeError(callback + ' is not a function') 33 | } 34 | var t = Object(ary), len = t.length >>> 0, k = len - 1, value 35 | if (arguments.length >= 3) { 36 | value = arguments[2] 37 | } else { 38 | while (k >= 0 && !(k in t)) { 39 | k-- 40 | } 41 | if (k < 0) { 42 | throw new TypeError('Reduce of empty array with no initial value') 43 | } 44 | value = t[k--] 45 | } 46 | for (; k >= 0; k--) { 47 | if (k in t) { 48 | value = callback(value, t[k], k, t) 49 | } 50 | } 51 | return value 52 | } 53 | -------------------------------------------------------------------------------- /src/createStore.js: -------------------------------------------------------------------------------- 1 | var ActionTypes = { 2 | INIT: '@@redux/INIT' 3 | } 4 | createStore.ActionTypes = ActionTypes 5 | /* 6 | * 7 | redux.createStore(reducer, initialState) 传入了reducer、initialState, 8 | 并返回一个store对象。 9 | store对象对外暴露了dispatch、getState、subscribe方法 10 | store对象通过getState() 获取内部状态 11 | initialState为 store 的初始状态,如果不传则为undefined 12 | store对象通过reducer来修改内部状态 13 | store对象创建的时候,内部会主动调用dispatch({ type: ActionTypes.INIT }); 14 | 来对内部状态进行初始化。 15 | 通过断点或者日志打印就可以看到,store对象创建的同时,reducer就会被调用进行初始化。 16 | * 17 | */ 18 | function createStore(reducer, preloadedState, enhancer) { 19 | if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') { 20 | enhancer = preloadedState 21 | preloadedState = undefined 22 | } 23 | 24 | if (typeof enhancer !== 'undefined') { 25 | if (typeof enhancer !== 'function') { 26 | throw new Error('Expected the enhancer to be a function.') 27 | } 28 | 29 | return enhancer(createStore)(reducer, preloadedState) 30 | } 31 | 32 | if (typeof reducer !== 'function') { 33 | throw new Error('Expected the reducer to be a function.') 34 | } 35 | 36 | var currentReducer = reducer 37 | var currentState = preloadedState 38 | var currentListeners = [] 39 | var nextListeners = currentListeners 40 | var isDispatching = false 41 | 42 | function ensureCanMutateNextListeners() { 43 | if (nextListeners === currentListeners) { 44 | nextListeners = currentListeners.slice()//复制一份 45 | } 46 | } 47 | 48 | 49 | function getState() { 50 | return currentState 51 | } 52 | 53 | 54 | function subscribe(listener) { 55 | if (typeof listener !== 'function') { 56 | throw new Error('Expected listener to be a function.') 57 | } 58 | 59 | var isSubscribed = true 60 | 61 | ensureCanMutateNextListeners() 62 | nextListeners.push(listener) 63 | 64 | return function unsubscribe() { 65 | if (!isSubscribed) { 66 | return 67 | } 68 | 69 | isSubscribed = false 70 | 71 | ensureCanMutateNextListeners() 72 | var index = nextListeners.indexOf(listener) 73 | nextListeners.splice(index, 1) 74 | } 75 | } 76 | 77 | 78 | function dispatch(action) { 79 | if (!avalon.isPlainObject(action)) { 80 | avalon.error('Actions 必须是一个朴素的JS对象') 81 | } 82 | 83 | if (typeof action.type !== 'string') { 84 | avalon.error('action必须定义type属性') 85 | } 86 | 87 | 88 | 89 | if (isDispatching) { 90 | avalon.error('Reducers may not dispatch actions.') 91 | } 92 | 93 | try { 94 | isDispatching = true 95 | currentState = currentReducer(currentState, action) 96 | } finally { 97 | isDispatching = false 98 | } 99 | 100 | var listeners = currentListeners = nextListeners 101 | for (var i = 0; i < listeners.length; i++) { 102 | listeners[i]() 103 | } 104 | 105 | return action 106 | } 107 | 108 | /** 109 | * Replaces the reducer currently used by the store to calculate the state. 110 | * 111 | * You might need this if your app implements code splitting and you want to 112 | * load some of the reducers dynamically. You might also need this if you 113 | * implement a hot reloading mechanism for Redux. 114 | * 115 | * @param {Function} nextReducer The reducer for the store to use instead. 116 | * @returns {void} 117 | */ 118 | function replaceReducer(nextReducer) { 119 | if (typeof nextReducer !== 'function') { 120 | throw new Error('Expected the nextReducer to be a function.') 121 | } 122 | 123 | currentReducer = nextReducer 124 | dispatch({type: ActionTypes.INIT}) 125 | } 126 | 127 | /** 128 | * Interoperability point for observable/reactive libraries. 129 | * @returns {observable} A minimal observable of state changes. 130 | * For more information, see the observable proposal: 131 | * https://github.com/zenparsing/es-observable 132 | */ 133 | 134 | 135 | // When a store is created, an "INIT" action is dispatched so that every 136 | // reducer returns their initial state. This effectively populates 137 | // the initial state tree. 138 | dispatch({type: ActionTypes.INIT}) 139 | 140 | return { 141 | dispatch: dispatch, 142 | subscribe: subscribe, 143 | getState: getState, 144 | replaceReducer: replaceReducer 145 | // [$$observable]: observable 146 | } 147 | } 148 | 149 | module.exports = createStore -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | if(typeof avalon !== 'function'){ 2 | throw 'mmDux need avalon2' 3 | } 4 | var createStore = require('./createStore') 5 | var combineReducers = require('./combineReducers') 6 | var bindActionCreator = require('./bindActionCreator') 7 | var applyMiddleware = require('./applyMiddleware') 8 | var compose = require('./compose') 9 | 10 | module.exports = { 11 | createStore: createStore, 12 | combineReducers: combineReducers, 13 | bindActionCreator: bindActionCreator, 14 | applyMiddleware: applyMiddleware, 15 | compose: compose 16 | } -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack') 2 | 3 | var path = require('path') 4 | var node_modules = path.resolve(__dirname, 'node_modules') 5 | var fs = require('fs') 6 | var avalonPath = path.resolve(node_modules, 'avalon2/dist/avalon.js') 7 | fs.readFile(avalonPath, 'utf8', function (e, text) { 8 | fs.writeFile(path.resolve(__dirname, './dist/avalon.js'), text) 9 | }) 10 | 11 | function heredoc(fn) { 12 | return fn.toString().replace(/^[^\/]+\/\*!?\s?/, ''). 13 | replace(/\*\/[^\/]+$/, '').trim().replace(/>\s*<') 14 | } 15 | var api = heredoc(function () { 16 | /* 17 | 18 | 19 | */ 20 | }) 21 | 22 | module.exports = { 23 | entry: { 24 | index: './src/index', //我们开发时的入口文件 25 | }, 26 | output: { 27 | path: path.join(__dirname, 'dist'), 28 | filename: 'mmDux.js', 29 | libraryTarget: 'umd', 30 | library: 'mmDux' 31 | }, //页面引用的文件 32 | plugins: [ 33 | new webpack.BannerPlugin('mmDux by 司徒正美\n' + api) 34 | ], 35 | module: { 36 | loaders: [ 37 | //http://react-china.org/t/webpack-extracttextplugin-autoprefixer/1922/4 38 | // https://github.com/b82/webpack-basic-starter/blob/master/webpack.config.js 39 | {test: /\.html$/, loader: 'raw!html-minify'}, 40 | {test: /\.(ttf|eot|svg|woff2?)((\?|#)[^\'\"]+)?$/, loader: 'file-loader?name=[name].[ext]'} 41 | 42 | ] 43 | }, 44 | 45 | plugins: [ 46 | new webpack.ProvidePlugin({ 47 | $: 'jquery', //加载$全局 48 | 'window.avalon':'avalon2' //加载 avalon 全局 [******这里必须强制 window.avalon] 49 | }), 50 | ], 51 | resolve: { 52 | alias: { 53 | 'avalon':path.resolve(node_modules,'avalon2/dist/avalon.js')//这里就可以改成avalon.modern 54 | }, 55 | 56 | extensions: ['.js', '', '.css'] 57 | } 58 | } 59 | 60 | --------------------------------------------------------------------------------