```用于在入口处包裹需要用到React的组件。本质上是将store放入context里
301 |
302 | - conncet方法用于将组件绑定Redux。本质上是HOC,封装掉了每个组件都要写的板式代码,增加了功能。
303 |
304 | - react-redux的高封装性让开发者感知不到context的存在,甚至感知不到Store的getState,subscribe,dispatch的存在。只要connect一下,数据一变就自动刷新React组件,非常方便。
305 |
306 | [slide]
307 |
308 | # Part 2
309 |
310 | - 结合React
311 |
312 | - 中间件
313 |
314 | - 异步Action
315 |
316 | [slide]
317 |
318 | # 中间件的作用
319 |
320 | - 在流程中插入功能
321 |
322 | - 要满足两个特性:一是扩展功能,二是可以被链式调用。
323 |
324 | [slide]
325 |
326 | # 例子
327 |
328 | - redux-logger中间件
329 |
330 | 
331 |
332 | [slide]
333 |
334 | # 需求:打印log
335 |
336 | - 需求:自动打印出Action对象和更新前后的state,便于调试和追踪数据变化流
337 |
338 | ``` JavaScript
339 | console.log('current state: ', store.getState());
340 | console.log('dispatching', action);
341 | store.dispatch(action);
342 | console.log('next state', store.getState());
343 | ```
344 |
345 | - 我们需要封装打印log的代码,否则让程序员在每个dispatch的地方写log是不可接受的
346 |
347 | [slide]
348 |
349 | # 打印log的中间件
350 |
351 | ``` JavaScript
352 | const preDispatch = store.dispatch;
353 |
354 | store.dispatch = (action) => {
355 | console.log('current state: ', store.getState());
356 | console.log('action: ', action);
357 | preDispatch(action);
358 | console.log('next state: ', store.getState());
359 | };
360 | ```
361 |
362 | - 上述就是增强版store.dispatch,这就是Redux的中间件
363 |
364 | [slide]
365 |
366 | # log中间件为何只能封装到dispatch里?
367 |
368 | - ~~Action~~ (HOW?plain object)
369 | - ~~Action Creator~~ (WHERE?not updated)
370 | - ~~Reducer~~ (OK...But not pure)
371 | - dispatch
372 |
373 | - Redux的中间件本质上就是增强dispatch
374 |
375 | [slide]
376 |
377 | # 多个中间件
378 |
379 | - 例如将上述打印logger的中间件拆成两个
380 |
381 | ``` JavaScript
382 | // 只打印出 Action
383 | export const loggerAction = (store) => {
384 | return (dispatch) => {
385 | return (action) => {
386 | console.log('action: ', action);
387 | dispatch(action);
388 | };
389 | };
390 | };
391 |
392 | // 只打印出 更新后的state
393 | export const loggerState = (store) => {
394 | return (dispatch) => {
395 | return (action) => {
396 | console.log('current state: ', store.getState());
397 | dispatch(action);
398 | console.log('next state', store.getState());
399 | };
400 | };
401 | };
402 | ```
403 |
404 | [slide]
405 |
406 | # 多个中间件就像洋葱圈
407 |
408 | 
409 |
410 | [slide]
411 |
412 | # 更优雅的链式调用
413 |
414 | - 前面的例子里中间件已经实现了链式调用,前一个中间件增强过的store作为参数传递给下一个中间件。但用起来不够优雅
415 |
416 | - Redux提供applyMiddleware方法,允许将所有中间件作为参数传递进去。我们来自己实现这个方法
417 |
418 | [slide]
419 |
420 | # applyMiddleware
421 |
422 | ``` JavaScript
423 | export const applyMiddleware = (store, middlewares) => {
424 | let dispatch = store.dispatch;
425 | middlewares.forEach((middleware) => {
426 | dispatch = middleware(store)(dispatch);
427 | });
428 |
429 | return {
430 | ...store,
431 | dispatch,
432 | };
433 | };
434 |
435 | let store = createStore(reducer);
436 | store = applyMiddleware(store, [loggerAction, loggerState]);
437 | ```
438 |
439 | [slide]
440 |
441 | # applyMiddleware官方版
442 |
443 | - 官方版的applyMiddleware将第一个参数store也被精简掉了
444 |
445 | ``` JavaScript
446 | export const applyMiddlewarePlus = (...middlewares) => (createStore) => (reducer) => {
447 | const store = createStore(reducer);
448 |
449 | let dispatch = store.dispatch;
450 | middlewares.forEach((middleware) => {
451 | dispatch = middleware(store)(dispatch);
452 | });
453 |
454 | return {
455 | ...store,
456 | dispatch,
457 | };
458 | };
459 | ```
460 |
461 | [slide]
462 |
463 | # 总结
464 |
465 | - Redux里的中间件就是增强Store.dispatch功能
466 |
467 | - 开发中间件,需要支持链式调用
468 |
469 | [slide]
470 |
471 | # Part 2
472 |
473 | - 结合React
474 |
475 | - 中间件
476 |
477 | - 异步Action
478 |
479 | [slide]
480 |
481 | # 什么是异步Action
482 |
483 | - 需要异步操作时(ajax请求,读取文件等),你需要异步Action
484 |
485 | - Action本质是plain object,不存在同步异步的概念。所谓异步Action,本质上是打包一系列Action动作
486 |
487 | [slide]
488 |
489 | # redux-thunk中间件
490 |
491 | - 例如网络请求数据:
492 |
493 | - (1)dispatch出请求服务器数据的Action
494 |
495 | - (2)等待服务器响应
496 |
497 | - (3)dispatch出结果Action(携带服务器返回了的数据或异常)去更新state
498 |
499 | - redux-thunk中间件的作用:将这三步封装到Action Creator里,以实现打包一系列Action动作的目的
500 |
501 | [slide]
502 |
503 | # redux-thunk实现方式
504 |
505 | - 常规的Action creator返回一个Action,但redux-thunk,允许你的Action creator还可以返回一个```function(dispatch, getState)```
506 |
507 | ``` JavaScript
508 | function createThunkMiddleware(extraArgument) {
509 | return function (_ref) {
510 | var dispatch = _ref.dispatch,
511 | getState = _ref.getState;
512 | return function (next) {
513 | return function (action) {
514 | if (typeof action === 'function') {
515 | return action(dispatch, getState, extraArgument);
516 | }
517 |
518 | return next(action);
519 | };
520 | };
521 | };
522 | }
523 | ```
524 |
525 | [slide]
526 |
527 | # 实现网络请求的异步Action
528 |
529 | - 第一步dispatch出请求服务器数据的Action
530 |
531 | ``` JavaScript
532 | const requestData = () => ({
533 | type: constant.REQUEST_DATA,
534 | });
535 | ```
536 |
537 | [slide]
538 |
539 | # 实现网络请求的异步Action
540 |
541 | - 第二步dispatch出结果Action(携带服务器返回了的数据或异常)去更新state
542 |
543 | ``` JavaScript
544 | const receiveData = (data) => ({
545 | type: constant.RECEIVE_DATA,
546 | data: data.msg,
547 | });
548 | ```
549 |
550 | [slide]
551 |
552 | # 实现网络请求的异步Action
553 |
554 | - 第三步用redux-thunk将这两步连起来
555 |
556 | ``` JavaScript
557 | const doFetchData = () => (dispatch) => {
558 | dispatch(requestData());
559 | return fetch('./api/fetchSampleData.json')
560 | .then((response) => response.json())
561 | .then((json) => dispatch(receiveData(json)));
562 | };
563 |
564 | export default {
565 | fetchDataAction: () => (dispatch) => {
566 | return dispatch(doFetchData());
567 | },
568 | };
569 | ```
570 |
571 | [slide]
572 |
573 | # 实现网络请求的异步Action
574 |
575 | - 完整版(加上了请求未完成不允许连续请求,减少服务器开销的逻辑)
576 |
577 | ``` JavaScript
578 | import fetch from 'isomorphic-fetch';
579 | import * as constant from '../configs/action';
580 | import { sleep } from '../lib/common';
581 |
582 | const requestData = () => ({
583 | type: constant.REQUEST_DATA,
584 | });
585 |
586 | const receiveData = (data) => ({
587 | type: constant.RECEIVE_DATA,
588 | data: data.msg,
589 | });
590 |
591 | const doFetchData = () => async(dispatch) => {
592 | dispatch(requestData());
593 | await sleep(1000); // Just 4 mock
594 | return fetch('./api/fetchSampleData.json')
595 | .then((response) => response.json())
596 | .then((json) => dispatch(receiveData(json)));
597 | };
598 |
599 | const canFetchData = (state) => {
600 | return !state.fetchData.fetching;
601 | };
602 |
603 | export default {
604 | fetchDataAction: () => (dispatch, getState) => {
605 | if (canFetchData(getState())) {
606 | return dispatch(doFetchData());
607 | }
608 | return Promise.resolve();
609 | },
610 | };
611 | ```
612 |
613 | [slide]
614 |
615 | # Part 1
616 |
617 | - 概述(三大原则,初始态流转图,更新态流转图)
618 |
619 | - Action(Action,Action Creator)
620 |
621 | - Reducer(纯函数,Reducer Creator,combineReducers)
622 |
623 | - Store(createStore,getState,dispatch,subscribe)
624 |
625 | # Part 2
626 |
627 | - 结合React(Provider,connect,用context实现原理,HOC)
628 |
629 | - 中间件(洋葱圈式增强dispatch,实现链式调用)
630 |
631 | - 异步Action(redux-thunk)
632 |
633 | [slide]
634 |
635 | # 脚手架目录结构
636 |
637 |
638 |

639 |
640 |
641 | [slide]
642 |
643 |
644 |

645 |
646 |
647 | # THE END
648 |
649 | ### THANK YOU
650 |
--------------------------------------------------------------------------------
/scripts/dev.js:
--------------------------------------------------------------------------------
1 | // require('babel-polyfill');
2 | require('babel-register')();
3 | require('./dev/index');
4 |
--------------------------------------------------------------------------------
/scripts/dev/index.js:
--------------------------------------------------------------------------------
1 | import open from 'open';
2 | import webpack from 'webpack';
3 | import WebpackDevServer from 'webpack-dev-server';
4 | import path from 'path';
5 | import webpackConfig from '../webpack.config';
6 |
7 | const compiler = webpack(webpackConfig);
8 |
9 | const SRC = path.join(process.cwd(), 'src');
10 | const MOCK_SERVER = path.join(process.cwd(), 'mock-server');
11 |
12 | const devServerOptions = {
13 | contentBase: [
14 | SRC,
15 | MOCK_SERVER,
16 | ],
17 | hot: true,
18 | historyApiFallback: true,
19 | stats: 'verbose',
20 | };
21 |
22 | const server = new WebpackDevServer(compiler, devServerOptions);
23 | let opened = false;
24 |
25 | const openBrowser = () => {
26 | const address = server.listeningApp.address();
27 | const url = `http://${address.address}:${address.port}`;
28 | console.log(` server started: ${url}`);
29 | open(`${url}/redux.html`);
30 | };
31 |
32 | compiler.plugin('done', () => {
33 | if (!opened) {
34 | opened = true;
35 | openBrowser();
36 | }
37 | });
38 |
39 | const startServer = new Promise((resolve, reject) => {
40 | server.listen(webpackConfig.devServer.port, webpackConfig.devServer.host, (err) => {
41 | if (err) {
42 | reject(err);
43 | } else {
44 | resolve();
45 | }
46 | });
47 | });
48 |
49 | const devServer = async() => {
50 | await startServer;
51 |
52 | const stdIn = process.stdin;
53 | stdIn.setEncoding('utf8');
54 | stdIn.on('data', openBrowser);
55 | };
56 |
57 | devServer().catch((ex) => {
58 | console.error(ex);
59 | });
60 |
61 |
--------------------------------------------------------------------------------
/scripts/webpack.config.js:
--------------------------------------------------------------------------------
1 | const webpack = require('webpack');
2 | const fs = require('fs');
3 | const path = require('path');
4 | const glob = require('glob');
5 | const HtmlWebpackPlugin = require('html-webpack-plugin');
6 | // const ExtractTextPlugin = require('extract-text-webpack-plugin');
7 | const PostcssImport = require('postcss-import');
8 | const precss = require('precss');
9 | const cssnext = require('postcss-cssnext');
10 |
11 | const ENV_DEVELOPMENT = 'development';
12 | const ENV_PRODUCTION = 'production';
13 | const HOST = process.env.HOST || `0.0.0.0`;
14 | const PORT = process.env.PORT || 9999;
15 |
16 | const SRC = path.join(process.cwd(), 'src');
17 | const BUILD = path.join(process.cwd(), 'build');
18 | const TEMPLATE = path.join(process.cwd(), 'template');
19 | const ENTRIES = path.join(SRC, 'entries');
20 | const BASEDIR = path.join(__dirname, '..');
21 |
22 | const NODE_ENV = process.env.NODE_ENV || 'production';
23 |
24 | const config = {
25 | // entry: {
26 | // common: [
27 | // // 'babel-polyfill',
28 | // 'react',
29 | // 'react-dom'
30 | // ]
31 | // },
32 | entry: {},
33 | output: {
34 | path: `${BUILD}`,
35 | filename: '[name]',
36 | publicPath: './'
37 | },
38 | devtool: 'eval',
39 | module: {
40 | loaders: [
41 | {
42 | test: /\.js$/,
43 | exclude: /node_modules/,
44 | use: ['babel-loader', 'react-hot-loader/webpack'],
45 | },
46 | {
47 | test: /\.(png|jpg|gif)$/,
48 | loader: 'url-loader?limit=2048000',
49 | },
50 | {
51 | test: /\.css$/,
52 | use: ['style-loader', 'css-loader'],
53 | },
54 | {
55 | test: /\.pcss$/,
56 | use: [
57 | 'style-loader',
58 | 'css-loader',
59 | {
60 | loader: 'postcss-loader',
61 | options: {
62 | plugins: function() {
63 | return [
64 | PostcssImport(),
65 | precss,
66 | cssnext,
67 | ];
68 | },
69 | },
70 | },
71 | ],
72 | },
73 | {
74 | enforce: 'pre',
75 | test: /\.js$/,
76 | include: [
77 | path.resolve(BASEDIR, 'src'),
78 | ],
79 | use: 'eslint-loader',
80 | }
81 | ]
82 | },
83 | plugins: [
84 | // new ExtractTextPlugin(`${ENTRIES}/[name]-[hash].pcss`),
85 | // new webpack.optimize.CommonsChunkPlugin('common', 'js/common.js'),
86 | // new webpack.NamedModulesPlugin(),
87 | ],
88 | };
89 |
90 |
91 | const entryFileNameList = glob.sync(path.join(SRC, '*/entries') + '/*.js');
92 | entryFileNameList.forEach((item) => {
93 | const {
94 | entry,
95 | plugins
96 | } = config;
97 |
98 | let fileName = path.basename(item, '.js');
99 | entry[fileName] = [`${item}`];
100 | console.log('entry[item]: ', entry[fileName]);
101 |
102 | entry[fileName].unshift(`webpack-dev-server/client?http://${HOST}:${PORT}`);
103 | entry[fileName].unshift(`webpack/hot/log-apply-result`);
104 |
105 | // hot reload
106 | entry[fileName].unshift(`webpack/hot/only-dev-server`);
107 | entry[fileName].unshift(`react-hot-loader/patch`);
108 |
109 | const htmlName = fileName.toLowerCase();
110 | plugins.push(
111 | new HtmlWebpackPlugin({
112 | template: `${TEMPLATE}/index.html`,
113 | filename: `${htmlName}.html`,
114 | hash: false,
115 | inject: 'body',
116 | chunks: [
117 | // 'common',
118 | fileName,
119 | ]
120 | })
121 | );
122 | });
123 |
124 | switch (NODE_ENV) {
125 | case ENV_DEVELOPMENT:
126 | console.log('dev start');
127 | config.output.publicPath = '/';
128 | config.devServer = {
129 | historyApiFallback: true,
130 | hot: true,
131 | inline: true,
132 | progress: true,
133 | stats: {
134 | colors: true,
135 | hash: false,
136 | version: false,
137 | timings: false,
138 | assets: false,
139 | chunks: false,
140 | modules: false,
141 | reasons: false,
142 | children: false,
143 | source: false,
144 | errors: true,
145 | errorDetails: true,
146 | warnings: true,
147 | publicPath: false
148 | },
149 | host: HOST,
150 | port: PORT
151 | };
152 |
153 | config.plugins.push(
154 | new webpack.HotModuleReplacementPlugin()
155 | );
156 | break;
157 | // case ENV_PRODUCTION:
158 | // config.plugins.push(
159 | // new webpack.optimize.UglifyJsPlugin({
160 | // compress: {
161 | // warnings: false
162 | // }
163 | // }),
164 | // new webpack.DefinePlugin({
165 | // 'process.env': {
166 | // NODE_ENV: JSON.stringify('production')
167 | // }
168 | // })
169 | // );
170 | // break;
171 | default:
172 | break;
173 | }
174 |
175 | module.exports = config;
176 |
--------------------------------------------------------------------------------
/src/originRedux/entries/redux.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { render } from 'react-dom';
3 | import { AppContainer } from 'react-hot-loader';
4 | import Demo from '../originRedux';
5 |
6 | render(
7 |
8 |
9 | ,
10 | document.getElementById('app'),
11 | );
12 |
13 | if (module.hot) {
14 | module.hot.accept('../originRedux', () => {
15 | const newDemo = require('../originRedux').default;
16 | render(
17 |
18 | {React.createElement(newDemo)}
19 | ,
20 | document.getElementById('app'),
21 | );
22 | });
23 | }
24 |
--------------------------------------------------------------------------------
/src/originRedux/originRedux.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { createStore } from 'redux';
3 | import { Button } from 'antd';
4 | import 'antd/dist/antd.css';
5 | import './originRedux.pcss';
6 |
7 | const reducer = (state, action) => {
8 | if (typeof state === 'undefined') {
9 | return 0;
10 | }
11 |
12 | switch (action.type) {
13 | case 'INCREMENT':
14 | return state + 1;
15 | case 'DECREMENT':
16 | return state - 1;
17 | case 'CLEAR_NUM':
18 | return 0;
19 | default:
20 | return state;
21 | }
22 | };
23 |
24 | const store = createStore(reducer);
25 |
26 | const update = () => {
27 | const valueEl = document.getElementsByClassName('numValue');
28 | valueEl[0].innerHTML = store.getState().toString();
29 | };
30 |
31 | store.subscribe(update);
32 |
33 | export default class Number extends Component {
34 |
35 | addNum = () => {
36 | store.dispatch({ type: 'INCREMENT' });
37 | };
38 |
39 | minusNum = () => {
40 | store.dispatch({ type: 'DECREMENT' });
41 | };
42 |
43 | clearNum = () => {
44 | store.dispatch({ type: 'CLEAR_NUM' });
45 | };
46 |
47 | render() {
48 | return (
49 |
50 |
origin Redux
51 | Current Number:
0
52 |
53 |
54 |
55 |
56 |
57 |
58 | );
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/originRedux/originRedux.pcss:
--------------------------------------------------------------------------------
1 | .wrap {
2 | margin: 20px;
3 | font-size: 18px;
4 | .numValue {
5 | color: red;
6 | }
7 | .numBtn {
8 | margin: 10px;
9 | &:first-child {
10 | margin-left: 0;
11 | }
12 | }
13 | }
14 |
15 |
--------------------------------------------------------------------------------
/src/originReduxAction/actions/number.js:
--------------------------------------------------------------------------------
1 | import * as constant from '../configs/action';
2 |
3 | export const incrementNum = () => ({
4 | type: constant.INCREMENT,
5 | });
6 |
7 | export const decrementNum = () => ({
8 | type: constant.DECREMENT,
9 | });
10 |
11 | export const clearNum = () => ({
12 | type: constant.CLEAR_NUM,
13 | });
14 |
--------------------------------------------------------------------------------
/src/originReduxAction/configs/action.js:
--------------------------------------------------------------------------------
1 | // action.type 常量
2 | export const INCREMENT = 'INCREMENT';
3 | export const DECREMENT = 'DECREMENT';
4 | export const CLEAR_NUM = 'CLEAR_NUM';
5 |
--------------------------------------------------------------------------------
/src/originReduxAction/entries/reduxaction.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { render } from 'react-dom';
3 | import { AppContainer } from 'react-hot-loader';
4 | import Demo from '../originReduxAction';
5 |
6 | render(
7 |
8 |
9 | ,
10 | document.getElementById('app'),
11 | );
12 |
13 | if (module.hot) {
14 | module.hot.accept('../originReduxAction', () => {
15 | const newDemo = require('../originReduxAction').default;
16 | render(
17 |
18 | {React.createElement(newDemo)}
19 | ,
20 | document.getElementById('app'),
21 | );
22 | });
23 | }
24 |
--------------------------------------------------------------------------------
/src/originReduxAction/originRedux.pcss:
--------------------------------------------------------------------------------
1 | .wrap {
2 | margin: 20px;
3 | font-size: 18px;
4 | .numValue {
5 | color: red;
6 | }
7 | .numBtn {
8 | margin: 10px;
9 | &:first-child {
10 | margin-left: 0;
11 | }
12 | }
13 | }
14 |
15 |
--------------------------------------------------------------------------------
/src/originReduxAction/originReduxAction.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { createStore } from 'redux';
3 | import { Button } from 'antd';
4 | import 'antd/dist/antd.css';
5 | import * as constant from './configs/action';
6 | import * as actions from './actions/number';
7 | import './originRedux.pcss';
8 |
9 | const reducer = (state, action) => {
10 | if (typeof state === 'undefined') {
11 | return 0;
12 | }
13 |
14 | switch (action.type) {
15 | case constant.INCREMENT:
16 | return state + 1;
17 | case constant.DECREMENT:
18 | return state - 1;
19 | case constant.CLEAR_NUM:
20 | return 0;
21 | default:
22 | return state;
23 | }
24 | };
25 |
26 | const store = createStore(reducer);
27 |
28 | const update = () => {
29 | const valueEl = document.getElementsByClassName('numValue');
30 | valueEl[0].innerHTML = store.getState().toString();
31 | };
32 |
33 | store.subscribe(update);
34 |
35 | export default class Number extends Component {
36 |
37 | addNum = () => {
38 | store.dispatch(actions.incrementNum());
39 | };
40 |
41 | minusNum = () => {
42 | store.dispatch(actions.decrementNum());
43 | };
44 |
45 | clearNum = () => {
46 | store.dispatch(actions.clearNum());
47 | };
48 |
49 | render() {
50 | return (
51 |
52 |
origin Redux action
53 | Current Number:
0
54 |
55 |
56 |
57 |
58 |
59 |
60 | );
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/originReduxCombineReducer/actions/alert.js:
--------------------------------------------------------------------------------
1 | import * as constant from '../configs/action';
2 |
3 | export default {
4 | toggleAlert: () => ({
5 | type: constant.TOGGLE_ALERT,
6 | }),
7 | };
8 |
--------------------------------------------------------------------------------
/src/originReduxCombineReducer/actions/index.js:
--------------------------------------------------------------------------------
1 | import number from './number';
2 | import alert from './alert';
3 |
4 | export default {
5 | number,
6 | alert,
7 | };
8 |
9 |
--------------------------------------------------------------------------------
/src/originReduxCombineReducer/actions/number.js:
--------------------------------------------------------------------------------
1 | import * as constant from '../configs/action';
2 |
3 | export default {
4 | incrementNum: () => ({
5 | type: constant.INCREMENT,
6 | }),
7 | decrementNum: () => ({
8 | type: constant.DECREMENT,
9 | }),
10 | clearNum: () => ({
11 | type: constant.CLEAR_NUM,
12 | }),
13 | };
14 |
--------------------------------------------------------------------------------
/src/originReduxCombineReducer/configs/action.js:
--------------------------------------------------------------------------------
1 | // action.type 常量
2 | export const INCREMENT = 'INCREMENT';
3 | export const DECREMENT = 'DECREMENT';
4 | export const CLEAR_NUM = 'CLEAR_NUM';
5 | export const TOGGLE_ALERT = 'TOGGLE_ALERT';
6 |
--------------------------------------------------------------------------------
/src/originReduxCombineReducer/entries/reduxcombinereducer.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { render } from 'react-dom';
3 | import { AppContainer } from 'react-hot-loader';
4 | import Demo from '../originReduxCombineReducer';
5 |
6 | render(
7 |
8 |
9 | ,
10 | document.getElementById('app'),
11 | );
12 |
13 | if (module.hot) {
14 | module.hot.accept('../originReduxCombineReducer', () => {
15 | const newDemo = require('../originReduxCombineReducer').default;
16 | render(
17 |
18 | {React.createElement(newDemo)}
19 | ,
20 | document.getElementById('app'),
21 | );
22 | });
23 | }
24 |
--------------------------------------------------------------------------------
/src/originReduxCombineReducer/lib/common.js:
--------------------------------------------------------------------------------
1 | export const createReducer = (initialState, handlers) => {
2 | return (state = initialState, action) => {
3 | if (handlers.hasOwnProperty(action.type)) {
4 | return handlers[action.type](state, action);
5 | }
6 | return state;
7 | };
8 | };
9 |
10 | export const combineReducers = (reducers) => {
11 | const reducerKeys = Object.keys(reducers);
12 | return (state = {}, action) => {
13 | const nextState = {};
14 | for (let i = 0; i < reducerKeys.length; i++) {
15 | const key = reducerKeys[i];
16 | const reducer = reducers[key];
17 | nextState[key] = reducer(state[key], action);
18 | }
19 | return {
20 | ...state,
21 | ...nextState,
22 | };
23 | };
24 | };
25 |
--------------------------------------------------------------------------------
/src/originReduxCombineReducer/originRedux.pcss:
--------------------------------------------------------------------------------
1 | .wrap {
2 | margin: 20px;
3 | font-size: 18px;
4 | .numValue {
5 | color: red;
6 | }
7 | .numBtn {
8 | margin: 10px;
9 | &:first-child {
10 | margin-left: 0;
11 | }
12 | }
13 | }
14 |
15 |
--------------------------------------------------------------------------------
/src/originReduxCombineReducer/originReduxCombineReducer.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { createStore, compose } from 'redux';
3 | import { Button, Alert } from 'antd';
4 | import 'antd/dist/antd.css';
5 | import reducer from './reducers/index';
6 | import actions from './actions/index';
7 | import './originRedux.pcss';
8 |
9 | const store = createStore(reducer, compose(
10 | window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__(), // eslint-disable-line
11 | ));
12 |
13 | const update = () => {
14 | const valueEl = document.getElementsByClassName('numValue');
15 | valueEl[0].innerHTML = store.getState().changeNumber.number;
16 |
17 | const alertEl = document.getElementsByClassName('alert');
18 | if (store.getState().toggleAlert.showAlert) {
19 | alertEl[0].style.display = 'block';
20 | } else {
21 | alertEl[0].style.display = 'none';
22 | }
23 | };
24 |
25 | store.subscribe(update);
26 |
27 | export default class Number extends Component {
28 |
29 | addNum = () => {
30 | store.dispatch(actions.number.incrementNum());
31 | };
32 |
33 | minusNum = () => {
34 | store.dispatch(actions.number.decrementNum());
35 | };
36 |
37 | clearNum = () => {
38 | store.dispatch(actions.number.clearNum());
39 | };
40 |
41 | toggleAlert = () => {
42 | store.dispatch(actions.alert.toggleAlert());
43 | };
44 |
45 | render() {
46 | return (
47 |
48 |
origin Redux combine reducer
49 | Current Number:
0
50 |
51 |
52 |
53 |
54 |
55 |
59 |
60 | );
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/originReduxCombineReducer/reducers/alert.js:
--------------------------------------------------------------------------------
1 | import * as constant from '../configs/action';
2 | import { createReducer } from '../lib/common';
3 |
4 | const initialState = {
5 | showAlert: false,
6 | };
7 |
8 | export default createReducer(initialState, {
9 | [constant.TOGGLE_ALERT]: (state, action) => {
10 | return {
11 | ...state,
12 | showAlert: !state.showAlert,
13 | };
14 | },
15 | });
16 |
--------------------------------------------------------------------------------
/src/originReduxCombineReducer/reducers/index.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux';
2 | // import { combineReducers } from '../lib/common';
3 | import changeNumber from './number';
4 | import toggleAlert from './alert';
5 |
6 | export default combineReducers({
7 | changeNumber,
8 | toggleAlert,
9 | });
10 |
--------------------------------------------------------------------------------
/src/originReduxCombineReducer/reducers/number.js:
--------------------------------------------------------------------------------
1 | import * as constant from '../configs/action';
2 | import { createReducer } from '../lib/common';
3 |
4 | const initialState = {
5 | number: 0,
6 | };
7 |
8 | export default createReducer(initialState, {
9 | [constant.INCREMENT]: (state, action) => {
10 | return {
11 | ...state,
12 | number: state.number + 1,
13 | };
14 | },
15 | [constant.DECREMENT]: (state, action) => {
16 | return {
17 | ...state,
18 | number: state.number - 1,
19 | };
20 | },
21 | [constant.CLEAR_NUM]: (state, action) => {
22 | return {
23 | ...state,
24 | number: 0,
25 | };
26 | },
27 | });
28 |
--------------------------------------------------------------------------------
/src/originReduxReducer/actions/number.js:
--------------------------------------------------------------------------------
1 | import * as constant from '../configs/action';
2 |
3 | export const incrementNum = () => ({
4 | type: constant.INCREMENT,
5 | });
6 |
7 | export const decrementNum = () => ({
8 | type: constant.DECREMENT,
9 | });
10 |
11 | export const clearNum = () => ({
12 | type: constant.CLEAR_NUM,
13 | });
14 |
--------------------------------------------------------------------------------
/src/originReduxReducer/configs/action.js:
--------------------------------------------------------------------------------
1 | // action.type 常量
2 | export const INCREMENT = 'INCREMENT';
3 | export const DECREMENT = 'DECREMENT';
4 | export const CLEAR_NUM = 'CLEAR_NUM';
5 |
--------------------------------------------------------------------------------
/src/originReduxReducer/entries/reduxreducer.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { render } from 'react-dom';
3 | import { AppContainer } from 'react-hot-loader';
4 | import Demo from '../originReduxReducer';
5 |
6 | render(
7 |
8 |
9 | ,
10 | document.getElementById('app'),
11 | );
12 |
13 | if (module.hot) {
14 | module.hot.accept('../originReduxReducer', () => {
15 | const newDemo = require('../originReduxReducer').default;
16 | render(
17 |
18 | {React.createElement(newDemo)}
19 | ,
20 | document.getElementById('app'),
21 | );
22 | });
23 | }
24 |
--------------------------------------------------------------------------------
/src/originReduxReducer/originRedux.pcss:
--------------------------------------------------------------------------------
1 | .wrap {
2 | margin: 20px;
3 | font-size: 18px;
4 | .numValue {
5 | color: red;
6 | }
7 | .numBtn {
8 | margin: 10px;
9 | &:first-child {
10 | margin-left: 0;
11 | }
12 | }
13 | }
14 |
15 |
--------------------------------------------------------------------------------
/src/originReduxReducer/originReduxReducer.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { createStore } from 'redux';
3 | import { Button } from 'antd';
4 | import 'antd/dist/antd.css';
5 | import reducer from './reducers/number';
6 | import * as actions from './actions/number';
7 | import './originRedux.pcss';
8 |
9 | const store = createStore(reducer);
10 |
11 | const update = () => {
12 | const valueEl = document.getElementsByClassName('numValue');
13 | valueEl[0].innerHTML = store.getState().number;
14 | };
15 |
16 | store.subscribe(update);
17 |
18 | export default class Number extends Component {
19 |
20 | addNum = () => {
21 | store.dispatch(actions.incrementNum());
22 | };
23 |
24 | minusNum = () => {
25 | store.dispatch(actions.decrementNum());
26 | };
27 |
28 | clearNum = () => {
29 | store.dispatch(actions.clearNum());
30 | };
31 |
32 | render() {
33 | return (
34 |
35 |
origin Redux reducer
36 | Current Number:
0
37 |
38 |
39 |
40 |
41 |
42 |
43 | );
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/originReduxReducer/reducers/number.js:
--------------------------------------------------------------------------------
1 | import * as constant from '../configs/action';
2 |
3 | const initialState = {
4 | number: 0,
5 | };
6 |
7 | export default (state = initialState, action) => {
8 | switch (action.type) {
9 | case constant.INCREMENT:
10 | return {
11 | ...state,
12 | number: state.number + 1,
13 | };
14 | case constant.DECREMENT:
15 | return {
16 | ...state,
17 | number: state.number - 1,
18 | };
19 | case constant.CLEAR_NUM:
20 | return {
21 | ...state,
22 | number: 0,
23 | };
24 | default:
25 | return state;
26 | }
27 | };
28 |
--------------------------------------------------------------------------------
/src/originReduxStore/actions/alert.js:
--------------------------------------------------------------------------------
1 | import * as constant from '../configs/action';
2 |
3 | export default {
4 | toggleAlert: () => ({
5 | type: constant.TOGGLE_ALERT,
6 | }),
7 | };
8 |
--------------------------------------------------------------------------------
/src/originReduxStore/actions/index.js:
--------------------------------------------------------------------------------
1 | import number from './number';
2 | import alert from './alert';
3 |
4 | export default {
5 | number,
6 | alert,
7 | };
8 |
9 |
--------------------------------------------------------------------------------
/src/originReduxStore/actions/number.js:
--------------------------------------------------------------------------------
1 | import * as constant from '../configs/action';
2 |
3 | export default {
4 | incrementNum: () => ({
5 | type: constant.INCREMENT,
6 | }),
7 | decrementNum: () => ({
8 | type: constant.DECREMENT,
9 | }),
10 | clearNum: () => ({
11 | type: constant.CLEAR_NUM,
12 | }),
13 | };
14 |
--------------------------------------------------------------------------------
/src/originReduxStore/configs/action.js:
--------------------------------------------------------------------------------
1 | // action.type 常量
2 | export const INCREMENT = 'INCREMENT';
3 | export const DECREMENT = 'DECREMENT';
4 | export const CLEAR_NUM = 'CLEAR_NUM';
5 | export const TOGGLE_ALERT = 'TOGGLE_ALERT';
6 |
--------------------------------------------------------------------------------
/src/originReduxStore/entries/reduxstore.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { render } from 'react-dom';
3 | import { AppContainer } from 'react-hot-loader';
4 | import Demo from '../originReduxStore';
5 |
6 | render(
7 |
8 |
9 | ,
10 | document.getElementById('app'),
11 | );
12 |
13 | if (module.hot) {
14 | module.hot.accept('../originReduxStore', () => {
15 | const newDemo = require('../originReduxStore').default;
16 | render(
17 |
18 | {React.createElement(newDemo)}
19 | ,
20 | document.getElementById('app'),
21 | );
22 | });
23 | }
24 |
--------------------------------------------------------------------------------
/src/originReduxStore/lib/common.js:
--------------------------------------------------------------------------------
1 | export const createReducer = (initialState, handlers) => {
2 | return (state = initialState, action) => {
3 | if (handlers.hasOwnProperty(action.type)) {
4 | return handlers[action.type](state, action);
5 | }
6 | return state;
7 | };
8 | };
9 |
10 | export const createStore = (reducer) => {
11 | let state = {};
12 | const listeners = [];
13 | const getState = () => state;
14 | const dispatch = (action) => {
15 | state = reducer(state, action);
16 | listeners.forEach((listener) => listener());
17 | };
18 | const subscribe = (listener) => listeners.push(listener);
19 |
20 | return {
21 | getState,
22 | dispatch,
23 | subscribe,
24 | };
25 | };
26 |
--------------------------------------------------------------------------------
/src/originReduxStore/originRedux.pcss:
--------------------------------------------------------------------------------
1 | .wrap {
2 | margin: 20px;
3 | font-size: 18px;
4 | .numValue {
5 | color: red;
6 | }
7 | .numBtn {
8 | margin: 10px;
9 | &:first-child {
10 | margin-left: 0;
11 | }
12 | }
13 | }
14 |
15 |
--------------------------------------------------------------------------------
/src/originReduxStore/originReduxStore.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { createStore, applyMiddleware, compose } from 'redux';
3 | // import { applyMiddleware, compose } from 'redux';
4 | import { createLogger } from 'redux-logger';
5 | import { Button, Alert } from 'antd';
6 | import 'antd/dist/antd.css';
7 | import reducer from './reducers/index';
8 | import actions from './actions/index';
9 | // import { createStore } from '../lib/common';
10 | import './originRedux.pcss';
11 |
12 | const logger = createLogger();
13 | const store = createStore(reducer, compose(
14 | applyMiddleware(logger),
15 | window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__(), // eslint-disable-line
16 | ));
17 |
18 | // const store = createStore(reducer);
19 |
20 | const update = () => {
21 | const valueEl = document.getElementsByClassName('numValue');
22 | valueEl[0].innerHTML = store.getState().changeNumber.number;
23 |
24 | const alertEl = document.getElementsByClassName('alert');
25 | if (store.getState().toggleAlert.showAlert) {
26 | alertEl[0].style.display = 'block';
27 | } else {
28 | alertEl[0].style.display = 'none';
29 | }
30 | };
31 |
32 | const cancelUpdate = store.subscribe(update);
33 |
34 | export default class Number extends Component {
35 |
36 | addNum = () => {
37 | store.dispatch(actions.number.incrementNum());
38 | };
39 |
40 | minusNum = () => {
41 | store.dispatch(actions.number.decrementNum());
42 | };
43 |
44 | clearNum = () => {
45 | store.dispatch(actions.number.clearNum());
46 | };
47 |
48 | toggleAlert = () => {
49 | store.dispatch(actions.alert.toggleAlert());
50 | };
51 |
52 | render() {
53 | return (
54 |
55 |
origin Redux store
56 | Current Number:
0
57 |
58 |
59 |
60 |
61 |
62 |
66 |
67 |
68 |
69 |
70 | );
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/originReduxStore/reducers/alert.js:
--------------------------------------------------------------------------------
1 | import * as constant from '../configs/action';
2 | import { createReducer } from '../lib/common';
3 |
4 | const initialState = {
5 | showAlert: false,
6 | };
7 |
8 | export default createReducer(initialState, {
9 | [constant.TOGGLE_ALERT]: (state, action) => {
10 | return {
11 | ...state,
12 | showAlert: !state.showAlert,
13 | };
14 | },
15 | });
16 |
--------------------------------------------------------------------------------
/src/originReduxStore/reducers/index.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux';
2 | import changeNumber from './number';
3 | import toggleAlert from './alert';
4 |
5 | export default combineReducers({
6 | changeNumber,
7 | toggleAlert,
8 | });
9 |
--------------------------------------------------------------------------------
/src/originReduxStore/reducers/number.js:
--------------------------------------------------------------------------------
1 | import * as constant from '../configs/action';
2 | import { createReducer } from '../lib/common';
3 |
4 | const initialState = {
5 | number: 0,
6 | };
7 |
8 | export default createReducer(initialState, {
9 | [constant.INCREMENT]: (state, action) => {
10 | return {
11 | ...state,
12 | number: state.number + 1,
13 | };
14 | },
15 | [constant.DECREMENT]: (state, action) => {
16 | return {
17 | ...state,
18 | number: state.number - 1,
19 | };
20 | },
21 | [constant.CLEAR_NUM]: (state, action) => {
22 | return {
23 | ...state,
24 | number: 0,
25 | };
26 | },
27 | });
28 |
--------------------------------------------------------------------------------
/src/reactRedux/actions/alert.js:
--------------------------------------------------------------------------------
1 | import * as constant from '../configs/action';
2 |
3 | export default {
4 | toggleAlert: () => ({
5 | type: constant.TOGGLE_ALERT,
6 | }),
7 | };
8 |
--------------------------------------------------------------------------------
/src/reactRedux/actions/index.js:
--------------------------------------------------------------------------------
1 | import number from './number';
2 | import alert from './alert';
3 |
4 | export default {
5 | number,
6 | alert,
7 | };
8 |
9 |
--------------------------------------------------------------------------------
/src/reactRedux/actions/number.js:
--------------------------------------------------------------------------------
1 | import * as constant from '../configs/action';
2 |
3 | export default {
4 | incrementNum: () => ({
5 | type: constant.INCREMENT,
6 | }),
7 | decrementNum: () => ({
8 | type: constant.DECREMENT,
9 | }),
10 | clearNum: () => ({
11 | type: constant.CLEAR_NUM,
12 | }),
13 | };
14 |
--------------------------------------------------------------------------------
/src/reactRedux/components/alert/alert.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { render } from 'react-dom';
3 | import { Button, Alert } from 'antd';
4 | import 'antd/dist/antd.css';
5 | import './alert.pcss';
6 |
7 | export default class AlertComponent extends Component {
8 | render() {
9 | const {
10 | showAlert,
11 | handleClickAlert,
12 | } = this.props;
13 |
14 | return (
15 |
16 |
17 | {
18 | showAlert ?
: null
19 | }
20 |
21 | );
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/reactRedux/components/alert/alert.pcss:
--------------------------------------------------------------------------------
1 | .numBtn {
2 | margin: 10px;
3 | &:first-child {
4 | margin-left: 0;
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/src/reactRedux/components/number/number.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { render } from 'react-dom';
3 | import { Button } from 'antd';
4 | import 'antd/dist/antd.css';
5 | import './number.pcss';
6 |
7 | export default class Number extends Component {
8 |
9 | render() {
10 | const {
11 | value,
12 | handleClickAdd,
13 | handleClickMinux,
14 | handleClickClear,
15 | } = this.props;
16 |
17 | return (
18 |
19 | Current Number:
{value}
20 |
21 |
22 |
23 |
24 |
25 |
26 | );
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/reactRedux/components/number/number.pcss:
--------------------------------------------------------------------------------
1 | .numValue {
2 | color: red;
3 | }
4 |
5 | .numBtn {
6 | margin: 10px;
7 | &:first-child {
8 | margin-left: 0;
9 | }
10 | }
11 |
12 |
--------------------------------------------------------------------------------
/src/reactRedux/configs/action.js:
--------------------------------------------------------------------------------
1 | // action.type 常量
2 | export const INCREMENT = 'INCREMENT';
3 | export const DECREMENT = 'DECREMENT';
4 | export const CLEAR_NUM = 'CLEAR_NUM';
5 | export const TOGGLE_ALERT = 'TOGGLE_ALERT';
6 |
--------------------------------------------------------------------------------
/src/reactRedux/containers/sample/sample.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { bindActionCreators } from 'redux';
3 | // import { bindActionCreators } from '../../lib/common';
4 | import { render } from 'react-dom';
5 | import { connect } from 'react-redux';
6 | // import connect from '../../lib/connect';
7 | import action from '../../actions/index';
8 | import NumberComponent from '../../components/number/number';
9 | import AlertComponent from '../../components/alert/alert';
10 | import './sample.pcss';
11 |
12 | class Sample extends Component {
13 |
14 | handleClickAdd = () => {
15 | this.props.incrementNum();
16 | };
17 |
18 | handleClickMinux = () => {
19 | this.props.decrementNum();
20 | };
21 |
22 | handleClickClear = () => {
23 | this.props.clearNum();
24 | };
25 |
26 | handleClickAlert = () => {
27 | this.props.toggleAlert();
28 | };
29 |
30 | render() {
31 | const {
32 | number,
33 | showAlert,
34 | } = this.props;
35 |
36 | return (
37 |
38 |
recat redux
39 |
45 |
49 |
50 | );
51 | }
52 | }
53 |
54 | const mapStateToProps = (state) => {
55 | return {
56 | number: state.changeNumber.number,
57 | showAlert: state.toggleAlert.showAlert,
58 | };
59 | };
60 |
61 | // const mapDispatchToProps = (dispatch) => {
62 | // return {
63 | // incrementNum: () => dispatch(action.number.incrementNum()),
64 | // decrementNum: () => dispatch(action.number.decrementNum()),
65 | // clearNum: () => dispatch(action.number.clearNum()),
66 | // toggleAlert: () => dispatch(action.alert.toggleAlert()),
67 | // };
68 | // };
69 |
70 | // const mapDispatchToProps = (dispatch) => {
71 | // return {
72 | // incrementNum: bindActionCreators(action.number.incrementNum, dispatch),
73 | // decrementNum: bindActionCreators(action.number.decrementNum, dispatch),
74 | // clearNum: bindActionCreators(action.number.clearNum, dispatch),
75 | // toggleAlert: bindActionCreators(action.alert.toggleAlert, dispatch),
76 | // };
77 | // };
78 |
79 | const mapDispatchToProps = {
80 | incrementNum: action.number.incrementNum,
81 | decrementNum: action.number.decrementNum,
82 | clearNum: action.number.clearNum,
83 | toggleAlert: action.alert.toggleAlert,
84 | };
85 |
86 | export default connect(
87 | mapStateToProps,
88 | mapDispatchToProps,
89 | )(Sample);
90 |
91 | // const mergeProps = (stateProps, dispatchProps, ownProps) => {
92 | // return {
93 | // ...ownProps,
94 | // ...stateProps,
95 | // incrementNum: dispatchProps.incrementNum, // 只暴露出 incrementNum
96 | // };
97 | // };
98 | //
99 | // const mergeProps = (stateProps, dispatchProps, ownProps) => {
100 | // return {
101 | // ...ownProps,
102 | // state: stateProps,
103 | // actions: {
104 | // ...dispatchProps,
105 | // ...ownProps.actions,
106 | // },
107 | // };
108 | // };
109 | //
110 | // export default connect(
111 | // mapStateToProps,
112 | // mapDispatchToProps,
113 | // mergeProps,
114 | // )(Sample);
115 |
--------------------------------------------------------------------------------
/src/reactRedux/containers/sample/sample.pcss:
--------------------------------------------------------------------------------
1 | .wrap {
2 | margin: 20px;
3 | font-size: 18px;
4 | }
5 |
6 |
--------------------------------------------------------------------------------
/src/reactRedux/entries/reactRedux.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { render } from 'react-dom';
3 | import { AppContainer } from 'react-hot-loader';
4 | import { createStore, applyMiddleware, compose } from 'redux';
5 | import { createLogger } from 'redux-logger';
6 | import { Provider } from 'react-redux';
7 | // import Provider from '../lib/Provider';
8 | import reducer from '../reducers/index';
9 | import Sample from '../containers/sample/sample';
10 |
11 | const logger = createLogger();
12 | const store = createStore(reducer, compose(
13 | applyMiddleware(logger),
14 | window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__(), // eslint-disable-line
15 | ));
16 |
17 | render(
18 |
19 |
20 |
21 |
22 | ,
23 | document.getElementById('app'),
24 | );
25 |
26 | if (module.hot) {
27 | module.hot.accept('../containers/sample/sample', () => {
28 | const newDemo = require('../containers/sample/sample').default;
29 | render(
30 |
31 |
32 | {React.createElement(newDemo)}
33 |
34 | ,
35 | document.getElementById('app'),
36 | );
37 | });
38 | }
39 |
--------------------------------------------------------------------------------
/src/reactRedux/lib/Provider.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | export default class Provider extends Component {
5 | static childContextTypes = {
6 | store: PropTypes.object,
7 | };
8 |
9 | getChildContext = () => {
10 | return { store: this.props.store };
11 | };
12 |
13 | render() {
14 | return ({this.props.children}
);
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/reactRedux/lib/common.js:
--------------------------------------------------------------------------------
1 | export const createReducer = (initialState, handlers) => {
2 | return (state = initialState, action) => {
3 | if (handlers.hasOwnProperty(action.type)) {
4 | return handlers[action.type](state, action);
5 | }
6 | return state;
7 | };
8 | };
9 |
10 | export const createStore = (reducer) => {
11 | let state = {};
12 | const listeners = [];
13 | const getState = () => state;
14 | const dispatch = (action) => {
15 | state = reducer(state, action);
16 | listeners.forEach((listener) => listener());
17 | };
18 | const subscribe = (listener) => listeners.push(listener);
19 |
20 | return {
21 | getState,
22 | dispatch,
23 | subscribe,
24 | };
25 | };
26 |
27 | export const bindActionCreators = (actionCreators, dispatch) => {
28 | return () => dispatch(actionCreators());
29 | };
30 |
--------------------------------------------------------------------------------
/src/reactRedux/lib/connect.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | const connect = (mapStateToProps, mapDispatchToProps) => (WrappedComponent) => {
5 | class Connect extends Component {
6 | static contextTypes = {
7 | store: PropTypes.object,
8 | };
9 |
10 | constructor() {
11 | super();
12 | this.state = { finalProps: {} };
13 | // this.finalDispatchToProps = {};
14 | }
15 |
16 | componentWillMount() {
17 | const { store } = this.context;
18 | // const tempDispatchToProps = {};
19 | // if (typeof mapDispatchToProps === 'object') {
20 | // const keys = Object.keys(mapDispatchToProps);
21 | // keys.forEach((key) => {
22 | // const actionCreator = mapDispatchToProps[key];
23 | // tempDispatchToProps[key] = () => store.dispatch(actionCreator());
24 | // });
25 | // this.finalDispatchToProps = () => tempDispatchToProps;
26 | // } else {
27 | // this.finalDispatchToProps = () => mapDispatchToProps(store.dispatch);
28 | // }
29 | this.updateProps();
30 | store.subscribe(this.updateProps);
31 | }
32 |
33 | updateProps = () => {
34 | const { store } = this.context;
35 | const stateProps = mapStateToProps(store.getState());
36 | const dispatchProps = mapDispatchToProps(store.dispatch);
37 | // const dispatchProps = this.finalDispatchToProps();
38 |
39 | const finalProps = {
40 | ...stateProps,
41 | ...dispatchProps,
42 | ...this.props,
43 | };
44 |
45 | this.setState({
46 | finalProps,
47 | });
48 | };
49 |
50 | render() {
51 | const { finalProps } = this.state;
52 | return ();
53 | }
54 | }
55 |
56 | return Connect;
57 | };
58 |
59 | export default connect;
60 |
--------------------------------------------------------------------------------
/src/reactRedux/reducers/alert.js:
--------------------------------------------------------------------------------
1 | import * as constant from '../configs/action';
2 | import { createReducer } from '../lib/common';
3 |
4 | const initialState = {
5 | showAlert: false,
6 | };
7 |
8 | export default createReducer(initialState, {
9 | [constant.TOGGLE_ALERT]: (state, action) => {
10 | return {
11 | ...state,
12 | showAlert: !state.showAlert,
13 | };
14 | },
15 | });
16 |
--------------------------------------------------------------------------------
/src/reactRedux/reducers/index.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux';
2 | import changeNumber from './number';
3 | import toggleAlert from './alert';
4 |
5 | export default combineReducers({
6 | changeNumber,
7 | toggleAlert,
8 | });
9 |
--------------------------------------------------------------------------------
/src/reactRedux/reducers/number.js:
--------------------------------------------------------------------------------
1 | import * as constant from '../configs/action';
2 | import { createReducer } from '../lib/common';
3 |
4 | const initialState = {
5 | number: 0,
6 | };
7 |
8 | export default createReducer(initialState, {
9 | [constant.INCREMENT]: (state, action) => {
10 | return {
11 | ...state,
12 | number: state.number + 1,
13 | };
14 | },
15 | [constant.DECREMENT]: (state, action) => {
16 | return {
17 | ...state,
18 | number: state.number - 1,
19 | };
20 | },
21 | [constant.CLEAR_NUM]: (state, action) => {
22 | return {
23 | ...state,
24 | number: 0,
25 | };
26 | },
27 | });
28 |
--------------------------------------------------------------------------------
/src/reactReduxAsync/actions/alert.js:
--------------------------------------------------------------------------------
1 | import * as constant from '../configs/action';
2 |
3 | export default {
4 | toggleAlert: () => ({
5 | type: constant.TOGGLE_ALERT,
6 | }),
7 | };
8 |
--------------------------------------------------------------------------------
/src/reactReduxAsync/actions/fetchData.js:
--------------------------------------------------------------------------------
1 | import fetch from 'isomorphic-fetch';
2 | import * as constant from '../configs/action';
3 | import { sleep } from '../lib/common';
4 |
5 | const requestData = () => ({
6 | type: constant.REQUEST_DATA,
7 | });
8 |
9 | const receiveData = (data) => ({
10 | type: constant.RECEIVE_DATA,
11 | data: data.msg,
12 | });
13 |
14 | const doFetchData = () => async(dispatch) => {
15 | dispatch(requestData());
16 | await sleep(1000); // Just 4 mock
17 | return fetch('./api/fetchSampleData.json')
18 | .then((response) => response.json())
19 | .then((json) => dispatch(receiveData(json)));
20 | };
21 |
22 | const canFetchData = (state) => {
23 | return !state.fetchData.fetching;
24 | };
25 |
26 | export default {
27 | fetchDataAction: () => (dispatch, getState) => {
28 | if (canFetchData(getState())) {
29 | return dispatch(doFetchData());
30 | }
31 | return Promise.resolve();
32 | },
33 | };
34 |
--------------------------------------------------------------------------------
/src/reactReduxAsync/actions/index.js:
--------------------------------------------------------------------------------
1 | import number from './number';
2 | import alert from './alert';
3 | import fetchDataAction from './fetchData';
4 |
5 | export default {
6 | number,
7 | alert,
8 | fetchDataAction,
9 | };
10 |
11 |
--------------------------------------------------------------------------------
/src/reactReduxAsync/actions/number.js:
--------------------------------------------------------------------------------
1 | import * as constant from '../configs/action';
2 |
3 | export default {
4 | incrementNum: () => ({
5 | type: constant.INCREMENT,
6 | }),
7 | decrementNum: () => ({
8 | type: constant.DECREMENT,
9 | }),
10 | clearNum: () => ({
11 | type: constant.CLEAR_NUM,
12 | }),
13 | };
14 |
--------------------------------------------------------------------------------
/src/reactReduxAsync/components/alert/alert.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { render } from 'react-dom';
3 | import { Button, Alert } from 'antd';
4 | import 'antd/dist/antd.css';
5 | import './alert.pcss';
6 |
7 | export default class AlertComponent extends Component {
8 | render() {
9 | const {
10 | showAlert,
11 | handleClickAlert,
12 | } = this.props;
13 |
14 | return (
15 |
16 |
17 | {
18 | showAlert ?
: null
19 | }
20 |
21 | );
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/reactReduxAsync/components/alert/alert.pcss:
--------------------------------------------------------------------------------
1 | .numBtn {
2 | margin: 10px;
3 | &:first-child {
4 | margin-left: 0;
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/src/reactReduxAsync/components/fetchData/fetchData.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { render } from 'react-dom';
3 | import { Button } from 'antd';
4 | import 'antd/dist/antd.css';
5 | import LoadingComponent from '../loading/loading';
6 | import './fetchData.pcss';
7 |
8 | export default class FetchDataComponent extends Component {
9 | render() {
10 | const {
11 | showloading,
12 | handleClickFetchData,
13 | } = this.props;
14 |
15 | return (
16 |
17 |
18 |
19 |
20 | );
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/reactReduxAsync/components/fetchData/fetchData.pcss:
--------------------------------------------------------------------------------
1 | .numBtn {
2 | margin: 10px;
3 | &:first-child {
4 | margin-left: 0;
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/src/reactReduxAsync/components/loading/images/page-loading.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JackZhangXL/react-redux-demo/81bfd927e4f786e6cbaed06b2e9306a5070ee3b9/src/reactReduxAsync/components/loading/images/page-loading.gif
--------------------------------------------------------------------------------
/src/reactReduxAsync/components/loading/loading.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import './loading.pcss';
3 |
4 | export default class Loading extends Component {
5 | render() {
6 | const {
7 | show,
8 | } = this.props;
9 |
10 | return (
11 |
12 | );
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/reactReduxAsync/components/loading/loading.pcss:
--------------------------------------------------------------------------------
1 | .loading {
2 | position: fixed;
3 | top: 0;
4 | left: 0;
5 | background-color: rgba(0, 0, 0, 0.3);
6 | background-image: url(./images/page-loading.gif);
7 | background-size: 50px;
8 | background-repeat: no-repeat;
9 | background-position: center;
10 | width: 100%;
11 | height: 100%;
12 | z-index: 11;
13 | }
14 |
15 | .hide {
16 | display: none;
17 | }
18 |
--------------------------------------------------------------------------------
/src/reactReduxAsync/components/number/number.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { render } from 'react-dom';
3 | import { Button } from 'antd';
4 | import 'antd/dist/antd.css';
5 | import './number.pcss';
6 |
7 | export default class Number extends Component {
8 |
9 | render() {
10 | const {
11 | value,
12 | handleClickAdd,
13 | handleClickMinux,
14 | handleClickClear,
15 | } = this.props;
16 |
17 | return (
18 |
19 | Current Number:
{value}
20 |
21 |
22 |
23 |
24 |
25 |
26 | );
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/reactReduxAsync/components/number/number.pcss:
--------------------------------------------------------------------------------
1 | .numValue {
2 | color: red;
3 | }
4 |
5 | .numBtn {
6 | margin: 10px;
7 | &:first-child {
8 | margin-left: 0;
9 | }
10 | }
11 |
12 |
--------------------------------------------------------------------------------
/src/reactReduxAsync/configs/action.js:
--------------------------------------------------------------------------------
1 | // action.type 常量
2 | export const INCREMENT = 'INCREMENT';
3 | export const DECREMENT = 'DECREMENT';
4 | export const CLEAR_NUM = 'CLEAR_NUM';
5 | export const TOGGLE_ALERT = 'TOGGLE_ALERT';
6 | export const REQUEST_DATA = 'REQUEST_DATA';
7 | export const RECEIVE_DATA = 'RECEIVE_DATA';
8 |
--------------------------------------------------------------------------------
/src/reactReduxAsync/containers/sample/sample.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { render } from 'react-dom';
3 | import { connect } from 'react-redux';
4 | import action from '../../actions/index';
5 | import NumberComponent from '../../components/number/number';
6 | import AlertComponent from '../../components/alert/alert';
7 | import FetchDataComponent from '../../components/fetchData/fetchData';
8 | import './sample.pcss';
9 |
10 | class Sample extends Component {
11 |
12 | handleClickAdd = () => {
13 | this.props.incrementNum();
14 | };
15 |
16 | handleClickMinux = () => {
17 | this.props.decrementNum();
18 | };
19 |
20 | handleClickClear = () => {
21 | this.props.clearNum();
22 | };
23 |
24 | handleClickAlert = () => {
25 | this.props.toggleAlert();
26 | };
27 |
28 | handleClickFetchData = () => {
29 | this.props.fetchDataAction();
30 | };
31 |
32 | render() {
33 | const {
34 | number,
35 | showAlert,
36 | fetching,
37 | data,
38 | } = this.props;
39 |
40 | return (
41 |
42 |
recat redux async
43 |
49 |
53 |
57 |
{ data !== null ? data.msg : '' }
58 |
59 | );
60 | }
61 | }
62 |
63 | export default connect((state) => ({
64 | number: state.changeNumber.number,
65 | showAlert: state.toggleAlert.showAlert,
66 | fetching: state.fetchData.fetching,
67 | data: state.fetchData.data,
68 | }), {
69 | incrementNum: action.number.incrementNum,
70 | decrementNum: action.number.decrementNum,
71 | clearNum: action.number.clearNum,
72 | toggleAlert: action.alert.toggleAlert,
73 | fetchDataAction: action.fetchDataAction.fetchDataAction,
74 | })(Sample);
75 |
--------------------------------------------------------------------------------
/src/reactReduxAsync/containers/sample/sample.pcss:
--------------------------------------------------------------------------------
1 | .wrap {
2 | margin: 20px;
3 | font-size: 18px;
4 | }
5 |
6 |
--------------------------------------------------------------------------------
/src/reactReduxAsync/entries/reactReduxAsync.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { render } from 'react-dom';
3 | import { AppContainer } from 'react-hot-loader';
4 | import { createStore, applyMiddleware, compose } from 'redux';
5 | import thunk from 'redux-thunk';
6 | // import { thunk } from '../lib/common';
7 | import { createLogger } from 'redux-logger';
8 | import { Provider } from 'react-redux';
9 | import reducer from '../reducers/index';
10 | import Sample from '../containers/sample/sample';
11 |
12 | const logger = createLogger();
13 | const store = createStore(reducer, compose(
14 | applyMiddleware(thunk, logger),
15 | window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__(), // eslint-disable-line
16 | ));
17 |
18 | render(
19 |
20 |
21 |
22 |
23 | ,
24 | document.getElementById('app'),
25 | );
26 |
27 | if (module.hot) {
28 | module.hot.accept('../containers/sample/sample', () => {
29 | const newDemo = require('../containers/sample/sample').default;
30 | render(
31 |
32 |
33 | {React.createElement(newDemo)}
34 |
35 | ,
36 | document.getElementById('app'),
37 | );
38 | });
39 | }
40 |
--------------------------------------------------------------------------------
/src/reactReduxAsync/lib/common.js:
--------------------------------------------------------------------------------
1 | export const createReducer = (initialState, handlers) => {
2 | return (state = initialState, action) => {
3 | if (handlers.hasOwnProperty(action.type)) {
4 | return handlers[action.type](state, action);
5 | }
6 | return state;
7 | };
8 | };
9 |
10 | export const createStore = (reducer) => {
11 | let state = {};
12 | const listeners = [];
13 | const getState = () => state;
14 | const dispatch = (action) => {
15 | state = reducer(state, action);
16 | listeners.forEach((listener) => listener());
17 | };
18 | const subscribe = (listener) => listeners.push(listener);
19 |
20 | return {
21 | getState,
22 | dispatch,
23 | subscribe,
24 | };
25 | };
26 |
27 | export const sleep = (timeout) => {
28 | return new Promise((resolve) => {
29 | setTimeout(resolve, timeout);
30 | });
31 | };
32 |
33 | export const thunk = (store) => (dispatch) => (action) => {
34 | if (typeof action === 'function') {
35 | return action(store.dispatch, store.getState);
36 | }
37 | return dispatch(action);
38 | };
39 |
--------------------------------------------------------------------------------
/src/reactReduxAsync/reducers/alert.js:
--------------------------------------------------------------------------------
1 | import * as constant from '../configs/action';
2 | import { createReducer } from '../lib/common';
3 |
4 | const initialState = {
5 | showAlert: false,
6 | };
7 |
8 | export default createReducer(initialState, {
9 | [constant.TOGGLE_ALERT]: (state, action) => {
10 | return {
11 | ...state,
12 | showAlert: !state.showAlert,
13 | };
14 | },
15 | });
16 |
--------------------------------------------------------------------------------
/src/reactReduxAsync/reducers/fetchData.js:
--------------------------------------------------------------------------------
1 | import * as constant from '../configs/action';
2 | import { createReducer } from '../lib/common';
3 |
4 | const initialState = {
5 | fetching: false,
6 | data: null,
7 | };
8 |
9 | export default createReducer(initialState, {
10 | [constant.REQUEST_DATA]: (state, action) => {
11 | return {
12 | ...state,
13 | fetching: true,
14 | };
15 | },
16 | [constant.RECEIVE_DATA]: (state, action) => {
17 | return {
18 | ...state,
19 | fetching: false,
20 | data: action.data,
21 | };
22 | },
23 | });
24 |
--------------------------------------------------------------------------------
/src/reactReduxAsync/reducers/index.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux';
2 | import changeNumber from './number';
3 | import toggleAlert from './alert';
4 | import fetchData from './fetchData';
5 |
6 | export default combineReducers({
7 | changeNumber,
8 | toggleAlert,
9 | fetchData,
10 | });
11 |
--------------------------------------------------------------------------------
/src/reactReduxAsync/reducers/number.js:
--------------------------------------------------------------------------------
1 | import * as constant from '../configs/action';
2 | import { createReducer } from '../lib/common';
3 |
4 | const initialState = {
5 | number: 0,
6 | };
7 |
8 | export default createReducer(initialState, {
9 | [constant.INCREMENT]: (state, action) => {
10 | return {
11 | ...state,
12 | number: state.number + 1,
13 | };
14 | },
15 | [constant.DECREMENT]: (state, action) => {
16 | return {
17 | ...state,
18 | number: state.number - 1,
19 | };
20 | },
21 | [constant.CLEAR_NUM]: (state, action) => {
22 | return {
23 | ...state,
24 | number: 0,
25 | };
26 | },
27 | });
28 |
--------------------------------------------------------------------------------
/src/reactReduxMiddleware/actions/alert.js:
--------------------------------------------------------------------------------
1 | import * as constant from '../configs/action';
2 |
3 | export default {
4 | toggleAlert: () => ({
5 | type: constant.TOGGLE_ALERT,
6 | }),
7 | };
8 |
--------------------------------------------------------------------------------
/src/reactReduxMiddleware/actions/index.js:
--------------------------------------------------------------------------------
1 | import number from './number';
2 | import alert from './alert';
3 |
4 | export default {
5 | number,
6 | alert,
7 | };
8 |
9 |
--------------------------------------------------------------------------------
/src/reactReduxMiddleware/actions/number.js:
--------------------------------------------------------------------------------
1 | import * as constant from '../configs/action';
2 |
3 | export default {
4 | incrementNum: () => ({
5 | type: constant.INCREMENT,
6 | }),
7 | decrementNum: () => ({
8 | type: constant.DECREMENT,
9 | }),
10 | clearNum: () => ({
11 | type: constant.CLEAR_NUM,
12 | }),
13 | };
14 |
--------------------------------------------------------------------------------
/src/reactReduxMiddleware/components/alert/alert.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { render } from 'react-dom';
3 | import { Button, Alert } from 'antd';
4 | import 'antd/dist/antd.css';
5 | import './alert.pcss';
6 |
7 | export default class AlertComponent extends Component {
8 | render() {
9 | const {
10 | showAlert,
11 | handleClickAlert,
12 | } = this.props;
13 |
14 | return (
15 |
16 |
17 | {
18 | showAlert ?
: null
19 | }
20 |
21 | );
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/reactReduxMiddleware/components/alert/alert.pcss:
--------------------------------------------------------------------------------
1 | .numBtn {
2 | margin: 10px;
3 | &:first-child {
4 | margin-left: 0;
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/src/reactReduxMiddleware/components/number/number.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { render } from 'react-dom';
3 | import { Button } from 'antd';
4 | import 'antd/dist/antd.css';
5 | import './number.pcss';
6 |
7 | export default class Number extends Component {
8 |
9 | render() {
10 | const {
11 | value,
12 | handleClickAdd,
13 | handleClickMinux,
14 | handleClickClear,
15 | } = this.props;
16 |
17 | return (
18 |
19 | Current Number:
{value}
20 |
21 |
22 |
23 |
24 |
25 |
26 | );
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/reactReduxMiddleware/components/number/number.pcss:
--------------------------------------------------------------------------------
1 | .numValue {
2 | color: red;
3 | }
4 |
5 | .numBtn {
6 | margin: 10px;
7 | &:first-child {
8 | margin-left: 0;
9 | }
10 | }
11 |
12 |
--------------------------------------------------------------------------------
/src/reactReduxMiddleware/configs/action.js:
--------------------------------------------------------------------------------
1 | // action.type 常量
2 | export const INCREMENT = 'INCREMENT';
3 | export const DECREMENT = 'DECREMENT';
4 | export const CLEAR_NUM = 'CLEAR_NUM';
5 | export const TOGGLE_ALERT = 'TOGGLE_ALERT';
6 |
--------------------------------------------------------------------------------
/src/reactReduxMiddleware/containers/sample/sample.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import PropTypes from 'prop-types';
3 | import { render } from 'react-dom';
4 | import { connect } from 'react-redux';
5 | import action from '../../actions/index';
6 | import NumberComponent from '../../components/number/number';
7 | import AlertComponent from '../../components/alert/alert';
8 | import './sample.pcss';
9 |
10 | class Sample extends Component {
11 |
12 | // static contextTypes = {
13 | // store: PropTypes.object,
14 | // };
15 |
16 | handleClickAdd = () => {
17 | // console.log('prevState: ', this.context.store.getState());
18 | // console.log('action: ', action.number.incrementNum());
19 | this.props.incrementNum();
20 | // console.log('currentState: ', this.context.store.getState());
21 | };
22 |
23 | handleClickMinux = () => {
24 | this.props.decrementNum();
25 | };
26 |
27 | handleClickClear = () => {
28 | this.props.clearNum();
29 | };
30 |
31 | handleClickAlert = () => {
32 | this.props.toggleAlert();
33 | };
34 |
35 | render() {
36 | const {
37 | number,
38 | showAlert,
39 | } = this.props;
40 |
41 | return (
42 |
43 |
recat redux middleware
44 |
50 |
54 |
55 | );
56 | }
57 | }
58 |
59 | const mapStateToProps = (state) => {
60 | return {
61 | number: state.changeNumber.number,
62 | showAlert: state.toggleAlert.showAlert,
63 | };
64 | };
65 |
66 | const mapDispatchToProps = {
67 | incrementNum: action.number.incrementNum,
68 | decrementNum: action.number.decrementNum,
69 | clearNum: action.number.clearNum,
70 | toggleAlert: action.alert.toggleAlert,
71 | };
72 |
73 | export default connect(
74 | mapStateToProps,
75 | mapDispatchToProps,
76 | )(Sample);
77 |
--------------------------------------------------------------------------------
/src/reactReduxMiddleware/containers/sample/sample.pcss:
--------------------------------------------------------------------------------
1 | .wrap {
2 | margin: 20px;
3 | font-size: 18px;
4 | }
5 |
6 |
--------------------------------------------------------------------------------
/src/reactReduxMiddleware/entries/reactReduxMiddleware.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { render } from 'react-dom';
3 | import { AppContainer } from 'react-hot-loader';
4 | import { createStore } from 'redux';
5 | import { Provider } from 'react-redux';
6 | import reducer from '../reducers/index';
7 | import Sample from '../containers/sample/sample';
8 | import {
9 | middleware1,
10 | middleware2,
11 | loggerAction,
12 | loggerState,
13 | applyMiddleware,
14 | applyMiddlewarePlus,
15 | compose,
16 | } from '../lib/middleware';
17 |
18 | // Step0 - 手写 console.log
19 | // const store = createStore(reducer);
20 |
21 | // Step1 - 自定义中间件
22 | // const store = createStore(reducer);
23 | // store.dispatch = middleware1(store.dispatch);
24 | // store.dispatch = middleware2(store.dispatch);
25 |
26 | // Step2 - 用 applyMiddleware 将中间件更优雅地链接起来
27 | // let store = createStore(reducer);
28 | // store = applyMiddleware(store, [loggerAction, loggerState]);
29 |
30 | // Step3 - 更优雅的 applyMiddleware
31 | // const store = applyMiddlewarePlus(loggerAction, loggerState)(createStore)(reducer);
32 |
33 | // Step4 - 增加 compose 绑定插件功能
34 | const store = createStore(reducer, compose(
35 | applyMiddlewarePlus(loggerAction, loggerState),
36 | window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__(), // eslint-disable-line
37 | ));
38 |
39 | render(
40 |
41 |
42 |
43 |
44 | ,
45 | document.getElementById('app'),
46 | );
47 |
48 | if (module.hot) {
49 | module.hot.accept('../containers/sample/sample', () => {
50 | const newDemo = require('../containers/sample/sample').default;
51 | render(
52 |
53 |
54 | {React.createElement(newDemo)}
55 |
56 | ,
57 | document.getElementById('app'),
58 | );
59 | });
60 | }
61 |
--------------------------------------------------------------------------------
/src/reactReduxMiddleware/lib/common.js:
--------------------------------------------------------------------------------
1 | export const createReducer = (initialState, handlers) => {
2 | return (state = initialState, action) => {
3 | if (handlers.hasOwnProperty(action.type)) {
4 | return handlers[action.type](state, action);
5 | }
6 | return state;
7 | };
8 | };
9 |
10 | export const createStore = (reducer) => {
11 | let state = {};
12 | const listeners = [];
13 | const getState = () => state;
14 | const dispatch = (action) => {
15 | state = reducer(state, action);
16 | listeners.forEach((listener) => listener());
17 | };
18 | const subscribe = (listener) => listeners.push(listener);
19 |
20 | return {
21 | getState,
22 | dispatch,
23 | subscribe,
24 | };
25 | };
26 |
--------------------------------------------------------------------------------
/src/reactReduxMiddleware/lib/middleware.js:
--------------------------------------------------------------------------------
1 | // Step1 自定义中间件
2 | export const middleware1 = (dispatch) => {
3 | return (action) => {
4 | console.log('first middleware');
5 | dispatch(action);
6 | };
7 | };
8 |
9 | export const middleware2 = (dispatch) => {
10 | return (action) => {
11 | console.log('second middleware');
12 | dispatch(action);
13 | };
14 | };
15 |
16 | // Step2 - 用 applyMiddleware 将中间件更优雅地链接起来
17 | // // 只打印出 Action
18 | // export const loggerAction = (store) => {
19 | // return (dispatch) => {
20 | // return (action) => {
21 | // console.log('action: ', action);
22 | // dispatch(action);
23 | // };
24 | // };
25 | // };
26 | //
27 | // // 只打印出 更新后的state
28 | // export const loggerState = (store) => {
29 | // return (dispatch) => {
30 | // return (action) => {
31 | // console.log('current state: ', store.getState());
32 | // dispatch(action);
33 | // console.log('next state', store.getState());
34 | // };
35 | // };
36 | // };
37 |
38 | /* es6 写法 */
39 | // 只打印出 Action
40 | export const loggerAction = (store) => (dispatch) => (action) => {
41 | console.log('action: ', action);
42 | dispatch(action);
43 | };
44 |
45 | // 只打印出 更新后的state
46 | export const loggerState = (store) => (dispatch) => (action) => {
47 | console.log('current state: ', store.getState());
48 | dispatch(action);
49 | console.log('next state', store.getState());
50 | };
51 |
52 | export const applyMiddleware = (store, middlewares) => {
53 | let dispatch = store.dispatch;
54 | middlewares.forEach((middleware) => {
55 | dispatch = middleware(store)(dispatch);
56 | });
57 |
58 | return {
59 | ...store,
60 | dispatch,
61 | };
62 | };
63 |
64 | // Step3 - 更优雅的 applyMiddleware
65 | export const applyMiddlewarePlus = (...middlewares) => (createStore) => (reducer) => {
66 | const store = createStore(reducer);
67 |
68 | let dispatch = store.dispatch;
69 | middlewares.forEach((middleware) => {
70 | dispatch = middleware(store)(dispatch);
71 | });
72 |
73 | return {
74 | ...store,
75 | dispatch,
76 | };
77 | };
78 |
79 | // Step4 - 增加 compose 绑定插件功能
80 | export const compose = (...funcs) => {
81 | return funcs.reduce((a, b) => (...args) => a(b(...args)));
82 | };
83 |
--------------------------------------------------------------------------------
/src/reactReduxMiddleware/reducers/alert.js:
--------------------------------------------------------------------------------
1 | import * as constant from '../configs/action';
2 | import { createReducer } from '../lib/common';
3 |
4 | const initialState = {
5 | showAlert: false,
6 | };
7 |
8 | export default createReducer(initialState, {
9 | [constant.TOGGLE_ALERT]: (state, action) => {
10 | return {
11 | ...state,
12 | showAlert: !state.showAlert,
13 | };
14 | },
15 | });
16 |
--------------------------------------------------------------------------------
/src/reactReduxMiddleware/reducers/index.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux';
2 | import changeNumber from './number';
3 | import toggleAlert from './alert';
4 |
5 | export default combineReducers({
6 | changeNumber,
7 | toggleAlert,
8 | });
9 |
--------------------------------------------------------------------------------
/src/reactReduxMiddleware/reducers/number.js:
--------------------------------------------------------------------------------
1 | import * as constant from '../configs/action';
2 | import { createReducer } from '../lib/common';
3 |
4 | const initialState = {
5 | number: 0,
6 | };
7 |
8 | export default createReducer(initialState, {
9 | [constant.INCREMENT]: (state, action) => {
10 | return {
11 | ...state,
12 | number: state.number + 1,
13 | };
14 | },
15 | [constant.DECREMENT]: (state, action) => {
16 | return {
17 | ...state,
18 | number: state.number - 1,
19 | };
20 | },
21 | [constant.CLEAR_NUM]: (state, action) => {
22 | return {
23 | ...state,
24 | number: 0,
25 | };
26 | },
27 | });
28 |
--------------------------------------------------------------------------------
/src/reactReduxSaga/actions/alert.js:
--------------------------------------------------------------------------------
1 | import * as constant from '../configs/action';
2 |
3 | export default {
4 | toggleAlert: () => ({
5 | type: constant.TOGGLE_ALERT,
6 | }),
7 | };
8 |
--------------------------------------------------------------------------------
/src/reactReduxSaga/actions/fetchData.js:
--------------------------------------------------------------------------------
1 | import * as constant from '../configs/action';
2 |
3 | export default {
4 | fetchDataAction: (data) => ({
5 | type: constant.FETCH_DATA,
6 | data,
7 | }),
8 | };
9 |
--------------------------------------------------------------------------------
/src/reactReduxSaga/actions/index.js:
--------------------------------------------------------------------------------
1 | import number from './number';
2 | import alert from './alert';
3 | import fetchDataAction from './fetchData';
4 |
5 | export default {
6 | number,
7 | alert,
8 | fetchDataAction,
9 | };
10 |
11 |
--------------------------------------------------------------------------------
/src/reactReduxSaga/actions/number.js:
--------------------------------------------------------------------------------
1 | import * as constant from '../configs/action';
2 |
3 | export default {
4 | incrementNum: () => ({
5 | type: constant.INCREMENT,
6 | }),
7 | decrementNum: () => ({
8 | type: constant.DECREMENT,
9 | }),
10 | clearNum: () => ({
11 | type: constant.CLEAR_NUM,
12 | }),
13 | };
14 |
--------------------------------------------------------------------------------
/src/reactReduxSaga/api/index.js:
--------------------------------------------------------------------------------
1 | import fetch from 'isomorphic-fetch';
2 |
3 | export const fetchDataApi = async(data) => {
4 | return fetch(`./api/fetchSampleData.json?num=${data}`)
5 | .then((response) => response.json());
6 | };
7 |
8 |
--------------------------------------------------------------------------------
/src/reactReduxSaga/components/alert/alert.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { Button, Alert } from 'antd';
3 | import 'antd/dist/antd.css';
4 | import './alert.pcss';
5 |
6 | export default class AlertComponent extends Component {
7 | render() {
8 | const {
9 | showAlert,
10 | handleClickAlert,
11 | } = this.props;
12 |
13 | return (
14 |
15 |
16 | {
17 | showAlert ?
: null
18 | }
19 |
20 | );
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/reactReduxSaga/components/alert/alert.pcss:
--------------------------------------------------------------------------------
1 | .numBtn {
2 | margin: 10px;
3 | &:first-child {
4 | margin-left: 0;
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/src/reactReduxSaga/components/fetchData/fetchData.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { Button } from 'antd';
3 | import 'antd/dist/antd.css';
4 | import LoadingComponent from '../loading/loading';
5 | import './fetchData.pcss';
6 |
7 | export default class FetchDataComponent extends Component {
8 | render() {
9 | const {
10 | showloading,
11 | handleClickFetchData,
12 | } = this.props;
13 |
14 | return (
15 |
16 |
17 |
18 |
19 | );
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/reactReduxSaga/components/fetchData/fetchData.pcss:
--------------------------------------------------------------------------------
1 | .numBtn {
2 | margin: 10px;
3 | &:first-child {
4 | margin-left: 0;
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/src/reactReduxSaga/components/loading/images/page-loading.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JackZhangXL/react-redux-demo/81bfd927e4f786e6cbaed06b2e9306a5070ee3b9/src/reactReduxSaga/components/loading/images/page-loading.gif
--------------------------------------------------------------------------------
/src/reactReduxSaga/components/loading/loading.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import './loading.pcss';
3 |
4 | export default class Loading extends Component {
5 | render() {
6 | const {
7 | show,
8 | } = this.props;
9 |
10 | return (
11 |
12 | );
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/reactReduxSaga/components/loading/loading.pcss:
--------------------------------------------------------------------------------
1 | .loading {
2 | position: fixed;
3 | top: 0;
4 | left: 0;
5 | background-color: rgba(0, 0, 0, 0.3);
6 | background-image: url(./images/page-loading.gif);
7 | background-size: 50px;
8 | background-repeat: no-repeat;
9 | background-position: center;
10 | width: 100%;
11 | height: 100%;
12 | z-index: 11;
13 | }
14 |
15 | .hide {
16 | display: none;
17 | }
18 |
--------------------------------------------------------------------------------
/src/reactReduxSaga/components/number/number.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { Button } from 'antd';
3 | import 'antd/dist/antd.css';
4 | import './number.pcss';
5 |
6 | export default class Number extends Component {
7 |
8 | render() {
9 | const {
10 | value,
11 | handleClickAdd,
12 | handleClickMinux,
13 | handleClickClear,
14 | } = this.props;
15 |
16 | return (
17 |
18 | Current Number:
{value}
19 |
20 |
21 |
22 |
23 |
24 |
25 | );
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/reactReduxSaga/components/number/number.pcss:
--------------------------------------------------------------------------------
1 | .numValue {
2 | color: red;
3 | }
4 |
5 | .numBtn {
6 | margin: 10px;
7 | &:first-child {
8 | margin-left: 0;
9 | }
10 | }
11 |
12 |
--------------------------------------------------------------------------------
/src/reactReduxSaga/configs/action.js:
--------------------------------------------------------------------------------
1 | // action.type 常量
2 | export const INCREMENT = 'INCREMENT';
3 | export const DECREMENT = 'DECREMENT';
4 | export const CLEAR_NUM = 'CLEAR_NUM';
5 | export const TOGGLE_ALERT = 'TOGGLE_ALERT';
6 | export const REQUEST_DATA = 'REQUEST_DATA';
7 | export const RECEIVE_DATA = 'RECEIVE_DATA';
8 | export const FETCH_DATA = 'FETCH_DATA';
9 | export const FETCH_DATA_FAILED = 'FETCH_DATA_FAILED';
10 | export const TOGGLE_ALERT_ASYNC = 'TOGGLE_ALERT_ASYNC';
11 |
--------------------------------------------------------------------------------
/src/reactReduxSaga/containers/sample/sample.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { connect } from 'react-redux';
3 | import action from '../../actions/index';
4 | import NumberComponent from '../../components/number/number';
5 | import AlertComponent from '../../components/alert/alert';
6 | import FetchDataComponent from '../../components/fetchData/fetchData';
7 | import './sample.pcss';
8 |
9 | class Sample extends Component {
10 |
11 | handleClickAdd = () => {
12 | this.props.incrementNum();
13 | };
14 |
15 | handleClickMinux = () => {
16 | this.props.decrementNum();
17 | };
18 |
19 | handleClickClear = () => {
20 | this.props.clearNum();
21 | };
22 |
23 | handleClickAlert = () => {
24 | this.props.toggleAlert();
25 | };
26 |
27 | handleClickFetchData = () => {
28 | this.props.fetchDataAction(1);
29 | };
30 |
31 | render() {
32 | const {
33 | number,
34 | showAlert,
35 | fetching,
36 | data,
37 | } = this.props;
38 |
39 | return (
40 |
41 |
recat redux saga
42 |
48 |
52 |
56 |
{ data !== null ? data.msg : '' }
57 |
58 | );
59 | }
60 | }
61 |
62 | export default connect((state) => ({
63 | number: state.changeNumber.number,
64 | showAlert: state.toggleAlert.showAlert,
65 | fetching: state.fetchData.fetching,
66 | data: state.fetchData.data,
67 | }), {
68 | incrementNum: action.number.incrementNum,
69 | decrementNum: action.number.decrementNum,
70 | clearNum: action.number.clearNum,
71 | toggleAlert: action.alert.toggleAlert,
72 | fetchDataAction: action.fetchDataAction.fetchDataAction,
73 | })(Sample);
74 |
--------------------------------------------------------------------------------
/src/reactReduxSaga/containers/sample/sample.pcss:
--------------------------------------------------------------------------------
1 | .wrap {
2 | margin: 20px;
3 | font-size: 18px;
4 | }
5 |
6 |
--------------------------------------------------------------------------------
/src/reactReduxSaga/entries/reactReduxSaga.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { render } from 'react-dom';
3 | import { AppContainer } from 'react-hot-loader';
4 | import { createStore, applyMiddleware, compose } from 'redux';
5 | // import { createLogger } from 'redux-logger';
6 | import createSagaMiddleware from 'redux-saga';
7 | import { Provider } from 'react-redux';
8 | import reducer from '../reducers/index';
9 | import Sample from '../containers/sample/sample';
10 | import rootSaga from '../sagas';
11 |
12 | // const logger = createLogger();
13 | const saga = createSagaMiddleware();
14 |
15 | const store = createStore(reducer, compose(
16 | applyMiddleware(saga),
17 | window.devToolsExtension ? window.devToolsExtension() : (f) => f,
18 | ));
19 |
20 | saga.run(rootSaga);
21 |
22 | render(
23 |
24 |
25 |
26 |
27 | ,
28 | document.getElementById('app'),
29 | );
30 |
31 | if (module.hot) {
32 | module.hot.accept('../containers/sample/sample', () => {
33 | const newDemo = require('../containers/sample/sample').default;
34 | render(
35 |
36 |
37 | {React.createElement(newDemo)}
38 |
39 | ,
40 | document.getElementById('app'),
41 | );
42 | });
43 | }
44 |
--------------------------------------------------------------------------------
/src/reactReduxSaga/io.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JackZhangXL/react-redux-demo/81bfd927e4f786e6cbaed06b2e9306a5070ee3b9/src/reactReduxSaga/io.js
--------------------------------------------------------------------------------
/src/reactReduxSaga/lib/common.js:
--------------------------------------------------------------------------------
1 | export const createReducer = (initialState, handlers) => {
2 | return (state = initialState, action) => {
3 | if (handlers.hasOwnProperty(action.type)) {
4 | return handlers[action.type](state, action);
5 | }
6 | return state;
7 | };
8 | };
9 |
10 | export const sleep = (timeout) => {
11 | return new Promise((resolve) => {
12 | setTimeout(resolve, timeout);
13 | });
14 | };
15 |
--------------------------------------------------------------------------------
/src/reactReduxSaga/proc.js:
--------------------------------------------------------------------------------
1 | import { is } from './utils'
2 | import { as, matcher } from './io'
3 |
4 | export const NOT_ITERATOR_ERROR = "proc argument must be an iterator"
5 |
6 | export default function proc(iterator, subscribe=()=>()=>{}, dispatch=()=>{}) {
7 |
8 | if(!is.iterator(iterator))
9 | throw new Error(NOT_ITERATOR_ERROR)
10 |
11 | let deferredInput, deferredEnd
12 | const canThrow = is.throw(iterator)
13 |
14 | const endP = new Promise((resolve, reject) => deferredEnd = {resolve, reject})
15 |
16 | const unsubscribe = subscribe(input => {
17 | if(deferredInput && deferredInput.match(input))
18 | deferredInput.resolve(input)
19 | })
20 |
21 | next()
22 | return endP
23 |
24 | function next(arg, isError) {
25 | deferredInput = null
26 | try {
27 | if(isError && !canThrow)
28 | throw arg
29 | const result = isError ? iterator.throw(arg) : iterator.next(arg)
30 |
31 | if(!result.done)
32 | runEffect(result.value).then(next, err => next(err, true))
33 | else {
34 | unsubscribe()
35 | deferredEnd.resolve(result.value)
36 | }
37 | } catch(err) {
38 | unsubscribe()
39 | deferredEnd.reject(err)
40 | }
41 | }
42 |
43 | function runEffect(effect) {
44 | let data
45 | return (
46 | is.promise(effect) ? effect
47 | : is.array(effect) ? Promise.all(effect.map(runEffect))
48 | : is.iterator(effect) ? proc(effect, subscribe, dispatch)
49 |
50 | : (data = as.take(effect)) ? runTakeEffect(data)
51 | : (data = as.put(effect)) ? Promise.resolve(dispatch(data))
52 | : (data = as.race(effect)) ? runRaceEffect(data)
53 | : (data = as.call(effect)) ? data.fn(...data.args)
54 | : (data = as.cps(effect)) ? runCPSEffect(data)
55 |
56 | : /* resolve anything else */ Promise.resolve(effect)
57 | )
58 | }
59 |
60 | function runTakeEffect(pattern) {
61 | return new Promise(resolve => {
62 | deferredInput = { resolve, match : matcher(pattern), pattern }
63 | })
64 | }
65 |
66 | function runCPSEffect(cps) {
67 | return new Promise((resolve, reject) => {
68 | cps.fn(...[
69 | ...cps.args,
70 | (err, res) => is.undef(err) ? resolve(res) : reject(err)
71 | ])
72 | })
73 | }
74 |
75 | function runRaceEffect(effects) {
76 | return Promise.race(
77 | Object.keys(effects)
78 | .map(key =>
79 | runEffect(effects[key])
80 | .then( result => ({ [key]: result }),
81 | error => Promise.reject({ [key]: error }))
82 | )
83 | )
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/src/reactReduxSaga/reducers/alert.js:
--------------------------------------------------------------------------------
1 | import * as constant from '../configs/action';
2 | import { createReducer } from '../lib/common';
3 |
4 | const initialState = {
5 | showAlert: false,
6 | };
7 |
8 | export default createReducer(initialState, {
9 | [constant.TOGGLE_ALERT_ASYNC]: (state, action) => {
10 | return {
11 | ...state,
12 | showAlert: !state.showAlert,
13 | };
14 | },
15 | });
16 |
--------------------------------------------------------------------------------
/src/reactReduxSaga/reducers/fetchData.js:
--------------------------------------------------------------------------------
1 | import * as constant from '../configs/action';
2 | import { createReducer } from '../lib/common';
3 |
4 | const initialState = {
5 | fetching: false,
6 | data: null,
7 | };
8 |
9 | export default createReducer(initialState, {
10 | [constant.REQUEST_DATA]: (state, action) => {
11 | return {
12 | ...state,
13 | fetching: true,
14 | };
15 | },
16 | [constant.RECEIVE_DATA]: (state, action) => {
17 | return {
18 | ...state,
19 | fetching: false,
20 | data: action.data.msg,
21 | };
22 | },
23 | [constant.FETCH_DATA_FAILED]: (state, action) => {
24 | return {
25 | ...state,
26 | fetching: false,
27 | data: { msg: action.errMsg },
28 | };
29 | },
30 | });
31 |
--------------------------------------------------------------------------------
/src/reactReduxSaga/reducers/index.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux';
2 | import changeNumber from './number';
3 | import toggleAlert from './alert';
4 | import fetchData from './fetchData';
5 |
6 | export default combineReducers({
7 | changeNumber,
8 | toggleAlert,
9 | fetchData,
10 | });
11 |
--------------------------------------------------------------------------------
/src/reactReduxSaga/reducers/number.js:
--------------------------------------------------------------------------------
1 | import * as constant from '../configs/action';
2 | import { createReducer } from '../lib/common';
3 |
4 | const initialState = {
5 | number: 0,
6 | };
7 |
8 | export default createReducer(initialState, {
9 | [constant.INCREMENT]: (state, action) => {
10 | return {
11 | ...state,
12 | number: state.number + 1,
13 | };
14 | },
15 | [constant.DECREMENT]: (state, action) => {
16 | return {
17 | ...state,
18 | number: state.number - 1,
19 | };
20 | },
21 | [constant.CLEAR_NUM]: (state, action) => {
22 | return {
23 | ...state,
24 | number: 0,
25 | };
26 | },
27 | });
28 |
--------------------------------------------------------------------------------
/src/reactReduxSaga/saga-middleware-backup.js:
--------------------------------------------------------------------------------
1 | function isGenerator(fn) {
2 | return fn.constructor.name === 'GeneratorFunction';
3 | }
4 |
5 | export default function sagaMiddleware(saga) {
6 | if (!isGenerator(saga)) {
7 | throw new Error('saga必须是个generator方法');
8 | }
9 |
10 | return ({ getState, dispatch }) => (next) => (action) => {
11 | const result = next(action);
12 | const gen = saga(getState, action);
13 |
14 | function iterate(generator) {
15 | function step(arg, isError) {
16 | const { value: effect, done } = isError ? generator.throw(arg) : generator.next(arg);
17 | // retreives next action/effect
18 | if (!done) {
19 | let response;
20 | if (typeof effect === 'function') {
21 | response = effect();
22 | } else if (Array.isArray(effect) && typeof effect[0] === 'function') {
23 | response = effect[0](...effect.slice(1));
24 | } else {
25 | response = dispatch(effect);
26 | }
27 |
28 | Promise.resolve(response).then(step, (err) => step(err, true));
29 | }
30 | }
31 | step();
32 | }
33 |
34 | iterate(gen);
35 |
36 | return result;
37 | };
38 | }
39 |
--------------------------------------------------------------------------------
/src/reactReduxSaga/saga-middleware.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JackZhangXL/react-redux-demo/81bfd927e4f786e6cbaed06b2e9306a5070ee3b9/src/reactReduxSaga/saga-middleware.js
--------------------------------------------------------------------------------
/src/reactReduxSaga/sagas-backup.js:
--------------------------------------------------------------------------------
1 | import { call, put, takeEvery, takeLatest, all, select, take } from 'redux-saga/effects';
2 | import { fetchDataApi } from './api/index';
3 | import * as constant from './configs/action';
4 | import { sleep } from './lib/common';
5 |
6 | function* alertAsync(action) {
7 | yield sleep(1000);
8 | yield put({ type: constant.TOGGLE_ALERT_ASYNC });
9 | }
10 |
11 | function* fetchData(action) { // 参数是 actionCreator 创建的 action
12 | try {
13 | yield put({ type: constant.REQUEST_DATA });
14 | yield sleep(1000);
15 | const data = yield call(fetchDataApi, action.data);
16 | yield put({ type: constant.RECEIVE_DATA, data });
17 | } catch (e) {
18 | yield put({ type: constant.FETCH_DATA_FAILED, errMsg: e.message });
19 | }
20 | }
21 |
22 | export function* watchAlertAsync() {
23 | yield takeEvery(constant.TOGGLE_ALERT, alertAsync);
24 | }
25 |
26 | export function* watchFetchData() {
27 | yield takeEvery(constant.FETCH_DATA, fetchData);
28 | }
29 |
30 | function* watchAndLog() {
31 | while (true) {
32 | const action = yield take('*');
33 | const state = yield select();
34 |
35 | console.log('action', action);
36 | console.log('state after', state);
37 | }
38 | // 和上面是等价的
39 | // yield takeEvery('*', function* logger(action) {
40 | // const state = yield select();
41 | //
42 | // console.log('action', action);
43 | // console.log('state after', state);
44 | // });
45 | }
46 |
47 | export default function* rootSaga() {
48 | yield all([
49 | watchAndLog(),
50 | watchAlertAsync(),
51 | watchFetchData(),
52 | ]);
53 | }
54 |
--------------------------------------------------------------------------------
/src/reactReduxSaga/sagas.js:
--------------------------------------------------------------------------------
1 | import { call, put, takeEvery, takeLatest, all, select, take } from 'redux-saga/effects';
2 | import { fetchDataApi } from './api/index';
3 | import * as constant from './configs/action';
4 | import { sleep } from './lib/common';
5 |
6 | function* alertAsync(action) {
7 | yield sleep(1000);
8 | yield put({ type: constant.TOGGLE_ALERT_ASYNC });
9 | }
10 |
11 | function* fetchData(action) { // 参数是 actionCreator 创建的 action
12 | try {
13 | yield put({ type: constant.REQUEST_DATA });
14 | yield sleep(1000);
15 | const data = yield call(fetchDataApi, action.data);
16 | yield put({ type: constant.RECEIVE_DATA, data });
17 | } catch (e) {
18 | yield put({ type: constant.FETCH_DATA_FAILED, errMsg: e.message });
19 | }
20 | }
21 |
22 | export function* watchAlertAsync() {
23 | yield takeEvery(constant.TOGGLE_ALERT, alertAsync);
24 | }
25 |
26 | export function* watchFetchData() {
27 | yield takeEvery(constant.FETCH_DATA, fetchData);
28 | }
29 |
30 | function* watchAndLog() {
31 | while (true) {
32 | const action = yield take('*');
33 | const state = yield select();
34 |
35 | console.log('action', action);
36 | console.log('state after', state);
37 | }
38 | // 和上面是等价的
39 | // yield takeEvery('*', function* logger(action) {
40 | // const state = yield select();
41 | //
42 | // console.log('action', action);
43 | // console.log('state after', state);
44 | // });
45 | }
46 |
47 | export default function* rootSaga() {
48 | yield all([
49 | watchAndLog(),
50 | watchAlertAsync(),
51 | watchFetchData(),
52 | ]);
53 | }
54 |
--------------------------------------------------------------------------------
/src/reactReduxSaga/utils.js:
--------------------------------------------------------------------------------
1 |
2 | export const kTrue = () => true
3 | export const noop = () => {}
4 |
5 | export const is = {
6 | undef : v => v === null || v === undefined,
7 | func : f => typeof f === 'function',
8 | array : Array.isArray,
9 | promise : p => p && typeof p.then === 'function',
10 | generator : g => g.constructor.name === 'GeneratorFunction',
11 | iterator : it => it && typeof it.next === 'function',
12 | throw : it => it && typeof it.throw === 'function'
13 | }
14 |
15 | export function remove(array, item) {
16 | const index = array.indexOf(item)
17 | if(index >= 0)
18 | array.splice(index, 1)
19 | }
20 |
--------------------------------------------------------------------------------
/template/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Redux
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------