`是比较好的方式。
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 | }
--------------------------------------------------------------------------------