├── .DS_Store ├── README.md ├── demo5 ├── .DS_Store ├── .babelrc ├── README.md ├── app │ ├── .DS_Store │ ├── actions │ │ └── counter.js │ ├── components │ │ └── Counter.js │ ├── containers │ │ └── App.js │ ├── index.html │ ├── index.js │ ├── reducers │ │ ├── counter.js │ │ └── index.js │ └── store │ │ └── configureStore.js ├── gulpfile.js ├── package.json └── webpack.config.js └── middleware.js /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangning0/redux_demo/715d22b77598ba1e161a05f45d7eff465a3d4959/.DS_Store -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Redux 学习之旅 2 | 3 | ## Redux三大原则 4 | * 单一的数据源 5 | 6 | 整个应用的 state 被储存在一棵 object tree 中,并且这个 object tree 只存在于唯一一个 store 中。一般来说,你会通过store.dispatch()将action传到store,并且每个action必须是javascript对象 7 | 8 | * State是只读的 9 | 10 | 惟一改变 state 的方法就是触发 action,action 是一个用于描述已发生事件的普通对象。 11 | 12 | * 使用纯函数来执行修改 13 | 14 | ## Action 15 | * 一般来说,你会通过store.dispatch()将action传到store,并且每个action必须是javascript对象 16 | 17 | * 事实上,创建action对象很少用这种每次直接声明对象的方式,更多的是通过一个创建函数,这个函数被称为Action Creator 18 | 19 | function addTodo(text) { 20 | return { 21 | type: ADD_TODO, 22 | text 23 | }; 24 | } 25 | ## Reducer 26 | 27 | * Redux中的reducer是一个纯函数,传入state和action,返回一个新的state tree,简单而纯粹的完成某一件具体的事情,没有依赖,简单而纯粹是它的标签。 28 | 29 | const counter = (state=0,action)=>{ 30 | switch (action.type) { 31 | case 'INCREMENT': 32 | return state + 1; 33 | case 'DECREMENT': 34 | return state - 1; 35 | default: 36 | return state; 37 | } 38 | } 39 | 40 | * 永远不要在reducer里面做这些操作 41 | 42 | * 修改传入参数 43 | * 执行有副作用的操作,如api请求和路由跳转 44 | * 调用非纯函数 Date.now() Math.random() 45 | ## Store 46 | 47 | store是用来维持应用所有的state树的一个对象。改变store内state唯一的途径是对她dispatcher一个action 48 | 49 | Store是一个具有一下四个方法的对象: 50 | 51 | * getState() 52 | 53 | 返回应用当前的state树 54 | 55 | * dispatch(action) 56 | 57 | 分发action,触发state变化的唯一途径,会使用当前getState()的结果和传入的action以同步方式的调用store的reduce函数,返回值被作为下一个state。从现在开始,这就成为了getState的返回值,同时变化监听器会触发。 58 | 59 | * subscribe(listener) 60 | 61 | 添加一个变化监听器,每当dispatch action的时候就会执行,state树中的一部分可能已经变化,这是一个底层api,多数情况下,不会直接使用它,会使用一些react的绑定 62 | 63 | * replaceReducer(nextReducer) 64 | 65 | ## Redux 的顶层api介绍 66 | 67 | * createStroe 68 | 69 | * 调用方式:createStrore(reducer,[initialState]) 70 | 71 | * combineReducers 72 | 73 | * 调用方式:comebineReducers(reducers) 74 | 75 | 76 | combineReducers({todos,counter}); 77 | 78 | import {combineReducers} from 'redux'; 79 | import * as reducers from './reducers'; 80 | const todoApp = combineReducers(reducers); 81 | 82 | * applyMiddleware 83 | 84 | * 调用方式:applyMiddleware(...middlewares) 85 | 86 | * bindActionCreators 87 | * 调用方式:bindActionCreators(actionCreators, dispatch) 88 | 89 | let actions = { 90 | addItem:(text)=>{ 91 | type:type.ADD_ITEM, 92 | text 93 | } 94 | } 95 | bindActionCreators(actions,dispatch) 96 | //等价于=> 97 | /* 98 | return { 99 | addItem:(text)=>dispatch({type:type.ADD_ITEM,text}) 100 | } 101 | */ 102 | * compose 103 | 104 | * 调用方式:compose(...functions) 105 | * compose 用来实现从右到左来组合传入的多个函数,它做的只是让你不使用深度右括号的情况下来写深度嵌套的函数,仅此而已。 106 | 107 | ## React-redux 108 | 109 | * 提供`connect`和`Provider` 110 | 111 | * Provider 这个模块作为整个App的容器,在原有的App Container的基础上再包上一层,它的工作很简单,接受Redux的store作为props,可以通过this.context.store访问到。 112 | 113 | * 这个模块算是真正意义上连接了redux 和 react,connect就是酱store中的必要数据作为props传递给React组件来render,并包装action creator用于在响应数据操作时dispatcher一个action 114 | * `connect([mapStateToProps],[mapDispatchToProps],[mergeProps],[options])` 115 | 116 | * mapStateToProps是一个函数,返回值表示的是需要merge进props的state。默认值为()=>({})什么都不传。`(state,props)=>({})` 117 | * mapDispatchToProps是可以是一个函数,返回值表示的是需要merge进props的actionCreators这里的actionCreator应该是已经包装了dispath了的,推荐使用redux的`bindActionCreators`函数 118 | 119 | (dispatch, props) => ({ // 通常会省略第二个参数 120 | ...bindActionCreators({ 121 | ...ResourceActions 122 | }, dispatch) 123 | }) 124 | 125 | //demo 126 | import * as actionCreators from './actionCreators' 127 | import { bindActionCreators } from 'redux' 128 | 129 | function mapStateToProps(state) { 130 | return { todos: state.todos } 131 | } 132 | 133 | function mapDispatchToProps(dispatch) { 134 | return { actions: bindActionCreators(actionCreators, dispatch) } 135 | } 136 | 137 | export default connect(mapStateToProps, mapDispatchToProps)(Component) 138 | 139 | 140 | ## 超酷的开发工具Redux-devtools 141 | 142 | `npm install --save-dev redux-devtools redux-devtools-log-monitor redux-devtools-dock-monitor` 143 | 144 | * redux-devtools:redux的开发工具包,而且DevTools支持自定义的 monitor 组件,所以我们可以完全自定义一个我们想要的 monitor 组件的UI展示风格,以下两个是我们示例中用到的。 145 | * redux-devtools-log-monitor: 这是Redux Devtools 默认的 monitor ,它可以展示state 和 action 的一系列信息,而且我们还可以在monitor改变它们的值。 146 | * redux-devtools-dock-monitor:这monitor支持键盘的快捷键来轻松的改变tree view在浏览器中的展示位置,比如不断的按‘ctrl + q’组合键可以让展示工具停靠在浏览器的上下左右不同的位置,按“ctrl+h”组合键则可以控制展示工具在浏览器的显示或隐藏的状态。 147 | 148 | 149 | import React from 'react'; 150 | import { createDevTools } from 'redux-devtools'; 151 | import LogMonitor from 'redux-devtools-log-monitor'; 152 | import DockMonitor from 'redux-devtools-dock-monitor'; 153 | 154 | export default createDevTools( 155 | 157 | 158 | 159 | ); 160 | 161 | // 162 | import React from 'react' 163 | import { render } from 'react-dom' 164 | import { Provider } from 'react-redux' 165 | import App from './containers/App' 166 | import DevTools from './containers/DevTools' 167 | import { createStore, compose } from 'redux'; 168 | import todoApp from './reducers' 169 | 170 | // 把多个 store 增强器从右到左来组合起来,依次执行 171 | // 这个地方完全可以不用compose,演示一下compose的使用 172 | const enhancer = compose( 173 | DevTools.instrument() 174 | ); 175 | 176 | let store = createStore(todoApp, enhancer) 177 | let rootElement = document.getElementById('app') 178 | 179 | render( 180 | 181 |
182 | 183 | 184 |
185 |
, 186 | rootElement 187 | ) 188 | 189 | # 理解和使用Middleware 190 | 191 | Redux中的middleware就是接受不同类型的action对象作为输入,负责改变store中的dispatch方法,从而达到我们预期的需求。它提供的是位于action被发起之后,到达reducer之前的扩展点 192 | 193 | 我们可以利用 Redux middleware 来进行日志记录、创建崩溃报告、调用异步接口或者路由等等。 194 | 195 | 可以使用redux的applyMiddleware方法将middlewares串起来,并返回一个增强型的 createStore 。 196 | 197 | compose 将从右到左来传入的多个函数组合起来 198 | 199 | ## 开发建议 200 | 201 | 开发复杂的应用时,不可避免的会有一些数据相互引用,建议尽可能的把state范式化,不存在嵌套。把所有数据放到一个对象里,每个数据以id为主键,不同数据相互应用时通过id来查找,把应用的state想象成数据库。`normalizr`文档有详细阐述,例如在实际开发中,在state里同时存放`todosById:{id->todo}`和`todos:array`是比较好的方式。 202 | 203 | 很多时候,我们经常要先把前面和后面都切开,可以选择帮助类[ React.addons.update](https://facebook.github.io/react/docs/update.html) [updeep](https://github.com/substantial/updeep) [Immutable](http://facebook.github.io/immutable-js/) 时刻谨记永远不要在克隆`state`前修改它 204 | 205 | 尽可能将嵌套的API响应范式化 206 | 207 | { 208 | selectedsubreddit: 'frontend', 209 | postsBySubreddit: { 210 | frontend: { 211 | isFetching: true, 212 | didInvalidate: false, 213 | items: [] 214 | }, 215 | reactjs: { 216 | isFetching: false, 217 | didInvalidate: false, 218 | lastUpdated: 1439478405547, 219 | items: [ 220 | { 221 | id: 42, 222 | title: 'Confusion about Flux and Relay' 223 | }, 224 | { 225 | id: 500, 226 | title: 'Creating a Simple Application Using React JS and Flux Architecture' 227 | } 228 | ] 229 | } 230 | } 231 | } 232 | 233 | //===>转化为 234 | 235 | { 236 | selectedsubreddit: 'frontend', 237 | entities: { 238 | users: { 239 | 2: { 240 | id: 2, 241 | name: 'Andrew' 242 | } 243 | }, 244 | posts: { 245 | 42: { 246 | id: 42, 247 | title: 'Confusion about Flux and Relay', 248 | author: 2 249 | }, 250 | 100: { 251 | id: 100, 252 | title: 'Creating a Simple Application Using React JS and Flux Architecture', 253 | author: 2 254 | } 255 | } 256 | }, 257 | postsBySubreddit: { 258 | frontend: { 259 | isFetching: true, 260 | didInvalidate: false, 261 | items: [] 262 | }, 263 | reactjs: { 264 | isFetching: false, 265 | didInvalidate: false, 266 | lastUpdated: 1439478405547, 267 | items: [ 42, 100 ] 268 | } 269 | } 270 | } 271 | ## 理解Middleware 272 | [Middleware](https://github.com/wangning0/redux_demo/blob/master/middleware.js) 273 | ## 长用的react插件 274 | * `redux-thunk` 275 | * `redux-promise` 276 | * `redux-actions` 277 | 278 | ## 异步Action 279 | 当调用异步API时,有两个非常关键的时刻:发起请求的时刻,和接收到响应的时刻(也可能时超时)。每个API请求都至少需要dispatch三个不同的action 280 | 281 | * 一个通知reducer请求开始的action 282 | * 一个通知reducer请求成功结束的action 283 | * 一个通知reducer请求失败的action 284 | 285 | 可是设置不同的字段来作为标记位 286 | 287 | ### 异步Action Creator 288 | 把定义的同步action creator 和网络请求结合起来呢?标准的做法是使用`redux thunk`middleware. 通过使用指定的 middleware,action creator 除了返回action对象外还可以返回函数,这个函数可以执行异步api请求,还可以dispatch action.就像dispatch前面定义的同步action一样 289 | 290 | ## 用于生成action creator 的函数 291 | -------------------------------------------------------------------------------- /demo5/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangning0/redux_demo/715d22b77598ba1e161a05f45d7eff465a3d4959/demo5/.DS_Store -------------------------------------------------------------------------------- /demo5/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "stage-0", "react"], 3 | "plugins": ["transform-runtime"] 4 | } 5 | -------------------------------------------------------------------------------- /demo5/README.md: -------------------------------------------------------------------------------- 1 | ##gulp构建的react项目脚手架 2 | 3 | * 分为开发环境和生产环境 4 | 5 | ### 步骤 6 | 7 | `npm install` 8 | 9 | 生产环境 `npm run pro` 10 | 11 | 开发环境 `npm run dev` 12 | -------------------------------------------------------------------------------- /demo5/app/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangning0/redux_demo/715d22b77598ba1e161a05f45d7eff465a3d4959/demo5/app/.DS_Store -------------------------------------------------------------------------------- /demo5/app/actions/counter.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by wangning on 16/4/14. 3 | */ 4 | export const INCREMENT_COUNTER = 'INCREMENT_COUNTER'; 5 | export const DECREMENT_COUNTRE = 'DECREMENT_COUNTRE'; 6 | 7 | export function increment() { 8 | return { 9 | type:INCREMENT_COUNTER 10 | } 11 | } 12 | 13 | export function decrement() { 14 | return { 15 | type:DECREMENT_COUNTRE 16 | } 17 | } 18 | 19 | export function incrementIfOdd() { 20 | return (dispatch,getState) => { 21 | const {counter} = getState() 22 | 23 | if( counter % 2 == 0 ){ 24 | return 25 | } 26 | dispatch(increment()) 27 | } 28 | } 29 | 30 | export function incrementAsync(delay = 1000) { 31 | return dispatch =>{ 32 | setTimeout(()=>{ 33 | dispatch(increment()) 34 | },delay) 35 | } 36 | } -------------------------------------------------------------------------------- /demo5/app/components/Counter.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by wangning on 16/4/14. 3 | */ 4 | import React , {Component,PropTypes} from 'react'; 5 | 6 | class Counter extends Component{ 7 | static propTypes = { 8 | increment:PropTypes.func.isRequired, 9 | incrementIfOdd:PropTypes.func.isRequired, 10 | incrementAsync:PropTypes.func.isRequired, 11 | decrement:PropTypes.func.isRequired, 12 | counter:PropTypes.number.isRequired 13 | } 14 | 15 | render(){ 16 | const {increment,incrementIfOdd,incrementAsync,decrement,counter} = this.props; 17 | console.log(this.props); 18 | return ( 19 |

20 | Clicked:{counter} times 21 | {' '} 22 | 23 | {' '} 24 | 25 | {' '} 26 | 27 | {' '} 28 | 29 |

30 | ) 31 | } 32 | } 33 | 34 | export default Counter;/** 35 | * Created by wangning on 16/4/14. 36 | */ 37 | -------------------------------------------------------------------------------- /demo5/app/containers/App.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by wangning on 16/4/14. 3 | */ 4 | 5 | import {bindActionCreators} from 'redux'; 6 | import {connect} from 'react-redux'; 7 | import Counter from '../components/Counter'; 8 | import * as CounterActions from '../actions/counter'; 9 | 10 | function mapStatetoProps(state) { 11 | return { 12 | counter:state.counter 13 | } 14 | } 15 | 16 | function mapDispatchToProps(dispatch) { 17 | return bindActionCreators(CounterActions,dispatch) 18 | } 19 | 20 | export default connect(mapStatetoProps,mapDispatchToProps)(Counter); 21 | 22 | -------------------------------------------------------------------------------- /demo5/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Counter 6 | 7 | 8 |
9 | 10 | -------------------------------------------------------------------------------- /demo5/app/index.js: -------------------------------------------------------------------------------- 1 | import {render} from 'react-dom'; 2 | import React from 'react'; 3 | import {Provider} from 'react-redux'; 4 | import App from './containers/App'; 5 | import configureStore from './store/configureStore'; 6 | 7 | let rootElement = document.getElementById('app'); 8 | const store = configureStore(); 9 | 10 | render( 11 | 12 | 13 | , 14 | rootElement 15 | ); -------------------------------------------------------------------------------- /demo5/app/reducers/counter.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by wangning on 16/4/14. 3 | */ 4 | import {INCREMENT_COUNTER,DECREMENT_COUNTRE} from '../actions/counter'; 5 | 6 | export default function counter(state = 0,action) { 7 | switch (action.type){ 8 | case INCREMENT_COUNTER: 9 | return state+1; 10 | case DECREMENT_COUNTRE: 11 | return state-1; 12 | default: 13 | return state; 14 | } 15 | } -------------------------------------------------------------------------------- /demo5/app/reducers/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by wangning on 16/4/14. 3 | */ 4 | 5 | import {combineReducers} from 'redux'; 6 | import counter from './counter'; 7 | 8 | const rootReducer = combineReducers({ 9 | counter 10 | }); 11 | 12 | export default rootReducer; -------------------------------------------------------------------------------- /demo5/app/store/configureStore.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by wangning on 16/4/14. 3 | */ 4 | 5 | import {createStore,applyMiddleware} from 'redux'; 6 | import thunk from 'redux-thunk'; 7 | import reducer from '../reducers/index'; 8 | 9 | const createStoreWithMiddleware = applyMiddleware(thunk)(createStore); 10 | 11 | export default function configureStore(initialState) { 12 | const store = createStoreWithMiddleware(reducer,initialState); 13 | return store; 14 | } -------------------------------------------------------------------------------- /demo5/gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var webpack = require('gulp-webpack'); 3 | var rimraf = require('gulp-rimraf'); 4 | var runSequence = require('run-sequence'); 5 | var WebpackConfig = require('./webpack.config.js'); 6 | var Webpack = require('webpack'); 7 | var WebpackDevServer = require('webpack-dev-server'); 8 | var gutil = require('gulp-util'); 9 | 10 | gulp.task('pro_build', function() { 11 | return gulp.src('./app/index.js') 12 | .pipe(webpack(WebpackConfig)) 13 | .pipe(gulp.dest('./build/')) 14 | }); 15 | gulp.task('clean', function() { 16 | gulp.src('./build', { 17 | read: false 18 | }) 19 | .pipe(rimraf()); 20 | 21 | return gulp.src('./server', { 22 | read: false 23 | }) 24 | .pipe(rimraf()); 25 | }); 26 | gulp.task('dev_build', function(cb) { 27 | new WebpackDevServer(Webpack(WebpackConfig), { 28 | contentBase: __dirname + './app/', 29 | hot: true, 30 | historyApiFallback: true, 31 | }).listen(8080, 'localhost', function(err) { 32 | if (err) throw new gutil.PluginError('webpack-dev-server', err); 33 | gutil.log('[webpack-dev-server]', 'http://example.com:8080'); 34 | }); 35 | }); 36 | gulp.task('pro', function() { 37 | runSequence('clean', 'pro_build'); 38 | }) 39 | gulp.task('dev', function() { 40 | runSequence('clean', 'dev_build'); 41 | }); -------------------------------------------------------------------------------- /demo5/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-gulp-hanging", 3 | "version": "1.0.0", 4 | "description": "the hanging of react item by gulp", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "mocha --compilers js:babel/register --recursive", 8 | "dev": "gulp dev", 9 | "pro": "gulp pro", 10 | "test:watch": "npm test -- --watch" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/wangning0/other/tree/master/react-gulo-hanging" 15 | }, 16 | "keywords": [ 17 | "react", 18 | "hanging", 19 | "gulp" 20 | ], 21 | "author": "WangNing", 22 | "license": "ISC", 23 | "devDependencies": { 24 | "babel": "^6.5.2", 25 | "babel-core": "^6.7.2", 26 | "babel-eslint": "^6.0.0", 27 | "babel-loader": "^6.2.4", 28 | "babel-plugin-transform-runtime": "^6.7.5", 29 | "babel-preset-es2015": "^6.6.0", 30 | "babel-preset-react": "^6.5.0", 31 | "babel-preset-stage-0": "^6.5.0", 32 | "babel-register": "^6.7.2", 33 | "css-loader": "^0.23.1", 34 | "extract-text-webpack-plugin": "^1.0.1", 35 | "file-loader": "^0.8.5", 36 | "gulp": "^3.9.1", 37 | "gulp-rimraf": "^0.2.0", 38 | "gulp-webpack": "^1.5.0", 39 | "html-webpack-plugin": "^2.10.0", 40 | "jsx-loader": "^0.13.2", 41 | "less": "^2.6.1", 42 | "less-loader": "^2.2.3", 43 | "mocha": "^2.4.5", 44 | "open-browser-webpack-plugin": "0.0.2", 45 | "react-hot-loader": "^1.3.0", 46 | "redux-thunk": "^2.0.1", 47 | "run-sequence": "^1.1.5", 48 | "style-loader": "^0.13.1", 49 | "uglify": "^0.1.5", 50 | "url-loader": "^0.5.7", 51 | "webpack": "^1.12.14", 52 | "webpack-dev-server": "^1.14.1" 53 | }, 54 | "dependencies": { 55 | "react": "^0.14.8", 56 | "react-dom": "^0.14.8", 57 | "react-redux": "^4.4.4", 58 | "redux": "^3.4.0" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /demo5/webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var webpack = require('webpack'); 3 | // 编译后自动打开浏览器 4 | // 产出html模板 5 | var HtmlWebpackPlugin = require("html-webpack-plugin"); 6 | // 单独样式文件 7 | var ExtractTextPlugin = require("extract-text-webpack-plugin"); 8 | var OpenBrowserPlugin = require('open-browser-webpack-plugin'); 9 | 10 | module.exports = { 11 | entry: [ 12 | 'webpack/hot/dev-server', 13 | 'webpack-dev-server/client?http://localhost:8080', 14 | path.resolve(__dirname, 'app/index.js') 15 | ], 16 | output: { 17 | path: path.resolve(__dirname, 'build'), 18 | filename: "[name].[hash:8].js", 19 | publicPath: '/' 20 | }, 21 | resolve: { 22 | extension: ['', '.jsx', '.js', '.json'], 23 | alias: {} 24 | }, 25 | 'display-error-details': true, 26 | module: { 27 | noParse: [], 28 | loaders: [{ 29 | test: /\.js[x]?$/, 30 | loaders: ['react-hot', 'babel'], 31 | exclude: path.resolve(__dirname, 'node_modules') 32 | }, { 33 | test: /\.css/, 34 | loader: ExtractTextPlugin.extract("style-loader", "css-loader") 35 | }, { 36 | test: /\.less/, 37 | loader: ExtractTextPlugin.extract("style-loader", "css-loader!less-loader") 38 | }, { 39 | test: /\.(png|jpg)$/, 40 | loader: 'url?limit=8192' 41 | }, { 42 | test: /\.(woff|woff2|ttf|svg|eot)(\?v=\d+\.\d+\.\d+)?$/, 43 | loader: "url?limit=10000" 44 | }] 45 | }, 46 | plugins: [ 47 | new ExtractTextPlugin("main.[hash:8].css", { 48 | allChunks: true, 49 | disable: false 50 | }), 51 | new HtmlWebpackPlugin({ 52 | title: 'your app title', 53 | template: './app/index.html', 54 | }), 55 | new OpenBrowserPlugin({ 56 | url: 'http://localhost:8080' 57 | }), 58 | new webpack.HotModuleReplacementPlugin(), 59 | new webpack.optimize.CommonsChunkPlugin('vendor', 'vendor.js') 60 | ] 61 | }; -------------------------------------------------------------------------------- /middleware.js: -------------------------------------------------------------------------------- 1 | //第一步解析middleware 假设要实现一个纪录日志 2 | let action = addTodo('Use Redux'); 3 | 4 | console.log('dispatching',action); 5 | store.dispatch(action); 6 | console.log('next state',store.getState()); 7 | 8 | //第二步,封装Dispatch 9 | 10 | function dispatchAndLog(store,action){ 11 | console.log('dispatching',action); 12 | store.dispatch(action); 13 | console.log('next state',store.getState()); 14 | } 15 | 16 | dispatchAndLog(store,addTodo('Use Redux')); 17 | 18 | //第三步,monkeypatching Dispatch (monkeypatching 给内置对象拓展方法) 19 | 20 | let next = store.dispatch; 21 | store.dispatch = function dispatchAndLog(action){ 22 | console.log('dispatching',action); 23 | let result = next(action); //返回值为action 24 | console.log('next state',store.getState()); 25 | return result; 26 | }; 27 | 28 | //不用的模块 29 | 30 | function patchStoreToAddLogging(store) { 31 | let next = store.dispatch; 32 | store.dispatch = function dispatchAndLog(action){ 33 | console.log('dispatching',action); 34 | let result = next(action); //返回值为action 35 | console.log('next state',store.getState()); 36 | return result; 37 | } 38 | } 39 | 40 | function patchStoreToAddCrashReporting(store){ 41 | let next = store.dispatch 42 | store.dispatch = function dispatchAndReportErrors(action) { 43 | try { 44 | return next(action) 45 | } catch (err) { 46 | console.error('捕获一个异常!', err) 47 | Raven.captureException(err, { 48 | extra: { 49 | action, 50 | state: store.getState() 51 | } 52 | }) 53 | throw err 54 | } 55 | } 56 | } 57 | 58 | //使用 59 | patchStoreToAddLogging(store); 60 | patchStoreToAddCrashReporting(store); 61 | 62 | 63 | //第四步,隐藏Monkeypatching 64 | //Monkeypatching 本质上也是一种hack"将任意的方法替换成你想要的",在之前,我们用自己的函数替换掉了 65 | //store.dispatch.如果我们不这么做,而是在函数中返回新的dispatch呢? 66 | 67 | function logger(store) { 68 | let next = store.dispatch; 69 | 70 | return function dispatchAndLog(action) { 71 | console.log('dispatching',action); 72 | let result = next(action); 73 | console.log('next state',store.getState()); 74 | return result; 75 | } 76 | } 77 | 78 | //在Redux内部提供一个可以将实际的monkeypatching应用到store.dispatch中的辅助方法 79 | 80 | function applyMiddlewareByMonkeypatching(store,middlewares) { 81 | middlewares = middlewares.slice(); 82 | middlewares.reverse(); 83 | 84 | middlewares.forEach(middleware=> 85 | store.dispatch = middleware(store); //会每个函数都会执行,返回值都是action会赋值给store.dispatch 86 | ) 87 | } 88 | 89 | //然后像这样应用多个middleware 90 | 91 | applyMiddlewareByMonkeypatching(store,[logger,patchStoreToAddCrashReporting]); 92 | 93 | 94 | //第五步 移除Monkeypatching 95 | 96 | //为什么我们要替换原来的dispatch呢? 97 | 98 | function logger(store) { 99 | return function wrapDispatchToAddLogging(next) { 100 | return function dispatchAndLog(action) { 101 | console.log('dispatching',action); 102 | let result = next(action); 103 | console.log('next state',store.getState()); 104 | return result; 105 | } 106 | } 107 | } 108 | 109 | //将ES6的箭头函数可以使其柯里化,从而看起来更舒服一些 110 | 111 | const logger = store => next => action => { 112 | console.log('dispatching',action); 113 | let result = next(action); 114 | console.log('next state',store.getState()); 115 | return result; 116 | }; 117 | 118 | const crashReporter = store => next => action => { 119 | try { 120 | return next(action) 121 | } catch (err) { 122 | console.error('Caught an exception!', err) 123 | Raven.captureException(err, { 124 | extra: { 125 | action, 126 | state: store.getState() 127 | } 128 | }) 129 | throw err 130 | } 131 | }; 132 | 133 | function applyMiddleware(store,middlewares) { 134 | middlewares = middlewares.slice(); 135 | middlewares.reverse(); 136 | 137 | let dispatch = store.dispatch; 138 | 139 | middlewares.forEach(middleware=> 140 | dispatch = middleware(store)(dispatch); 141 | ) 142 | 143 | return Object.assign({},store,{dispatch}); 144 | 145 | } --------------------------------------------------------------------------------