├── redux-async-demo ├── .gitignore ├── .babelrc ├── index.html ├── src │ ├── router.jsx │ ├── sagas │ │ └── index.jsx │ ├── app.js │ ├── components │ │ ├── index.jsx │ │ ├── PostList │ │ │ └── index.jsx │ │ ├── Sider │ │ │ └── index.jsx │ │ └── UserList │ │ │ └── index.jsx │ └── reducers │ │ └── index.js ├── README.md ├── package.json ├── asset │ └── css │ │ └── style.scss └── webpack.config.js ├── redux-router-v4-demo ├── .gitignore ├── .babelrc ├── index.html ├── src │ ├── sagas │ │ └── index.jsx │ ├── app.jsx │ ├── components │ │ ├── PostList │ │ │ └── index.jsx │ │ ├── Sider │ │ │ └── index.jsx │ │ ├── UserList │ │ │ └── index.jsx │ │ └── index.jsx │ └── reducers │ │ └── index.js ├── README.md ├── package.json ├── asset │ └── css │ │ └── style.scss └── webpack.config.js ├── 03_redux-router-v4-optimize ├── .gitignore ├── src │ ├── constant │ │ ├── url.js │ │ └── actionTypes.js │ ├── container │ │ ├── PostList.js │ │ ├── UserList.js │ │ └── index.jsx │ ├── redux │ │ ├── reducer │ │ │ ├── posts.js │ │ │ ├── users.js │ │ │ └── index.js │ │ └── sagas │ │ │ └── index.jsx │ ├── app.jsx │ └── component │ │ ├── PostList │ │ └── index.jsx │ │ ├── UserList │ │ └── index.jsx │ │ └── Sider │ │ └── index.jsx ├── .babelrc ├── index.html ├── package.json ├── asset │ └── css │ │ └── style.scss ├── README.md └── webpack.config.js └── README.md /redux-async-demo/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | ./npm-debug.log 3 | .DS_Store -------------------------------------------------------------------------------- /redux-router-v4-demo/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | ./npm-debug.log 3 | .DS_Store -------------------------------------------------------------------------------- /03_redux-router-v4-optimize/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | ./npm-debug.log 3 | .DS_Store -------------------------------------------------------------------------------- /03_redux-router-v4-optimize/src/constant/url.js: -------------------------------------------------------------------------------- 1 | export const GET_USERS_URL = 'https://jsonplaceholder.typicode.com/users'; 2 | export const GET_POSTS_URL = 'https://jsonplaceholder.typicode.com/posts'; -------------------------------------------------------------------------------- /redux-async-demo/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "react", 4 | "es2015", 5 | "stage-0" // 运行es6的扩展等语法 6 | ], 7 | "plugins": [ 8 | "transform-runtime" // 用于运行generator 9 | ] 10 | } -------------------------------------------------------------------------------- /redux-router-v4-demo/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "react", 4 | "es2015", 5 | "stage-0" // 运行es6的扩展等语法 6 | ], 7 | "plugins": [ 8 | "transform-runtime" // 用于运行generator 9 | ] 10 | } -------------------------------------------------------------------------------- /03_redux-router-v4-optimize/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "react", 4 | "es2015", 5 | "stage-0" // 运行es6的扩展等语法 6 | ], 7 | "plugins": [ 8 | "transform-runtime" // 用于运行generator 9 | ] 10 | } -------------------------------------------------------------------------------- /03_redux-router-v4-optimize/src/constant/actionTypes.js: -------------------------------------------------------------------------------- 1 | // ================ action types ================ 2 | // thunk 3 | export const GET_USERS_SUCESS = 'GET_USERS_SUCESS'; 4 | export const GET_USERS_FAIL = 'GET_USERS_FAIL'; 5 | 6 | // saga 7 | export const GET_POSTS_SAGA = 'GET_POSTS_SAGA'; 8 | export const GET_POSTS_SUCCESS = 'GET_POSTS_SUCCESS'; 9 | export const GET_POSTS_FAIL = 'GET_POSTS_FAIL'; 10 | -------------------------------------------------------------------------------- /redux-async-demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | react-webpack-demo 6 | 7 | 8 | 9 |
10 |
11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /redux-router-v4-demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | react-webpack-demo 6 | 7 | 8 | 9 |
10 |
11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /03_redux-router-v4-optimize/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | react-webpack-demo 6 | 7 | 8 | 9 |
10 |
11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /03_redux-router-v4-optimize/src/container/PostList.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import axios from 'axios'; 3 | 4 | import { GET_POSTS_SAGA } from 'constant/actionTypes'; 5 | import PostList from 'component/PostList'; 6 | 7 | const mapStateToProps = (state) => ({ 8 | // posts: state.posts // 合并的reducer 9 | posts: state.posts.posts // 单独的reducer 10 | }); 11 | 12 | const mapDispatchToProps = (dispatch) => ({ 13 | fetchPosts: () => dispatch({ type: GET_POSTS_SAGA }) 14 | }); 15 | 16 | export default connect(mapStateToProps, mapDispatchToProps)(PostList); -------------------------------------------------------------------------------- /03_redux-router-v4-optimize/src/redux/reducer/posts.js: -------------------------------------------------------------------------------- 1 | import { 2 | GET_POSTS_SUCCESS, 3 | GET_POSTS_FAIL 4 | } from 'constant/actionTypes'; 5 | 6 | const postReducer = (state = { 7 | fetched: false, 8 | posts: [{ 9 | key: '1', 10 | id: '1', 11 | title: 'test' 12 | }], 13 | error: null 14 | }, action) => { 15 | switch(action.type) { 16 | case GET_POSTS_SUCCESS: 17 | return {...state, fetched: true, posts: action.posts} 18 | case GET_POSTS_FAIL: 19 | return {...state, error: action.error} 20 | } 21 | return state; 22 | } 23 | 24 | 25 | export default postReducer -------------------------------------------------------------------------------- /03_redux-router-v4-optimize/src/redux/reducer/users.js: -------------------------------------------------------------------------------- 1 | import { 2 | GET_USERS_SUCESS, 3 | GET_USERS_FAIL, 4 | } from 'constant/actionTypes'; 5 | 6 | const userReducer = (state = { 7 | fetched: false, 8 | users: [{ 9 | key: '1', 10 | name: '张三', 11 | email: 'zhangsan@126.com' 12 | }], 13 | error: null 14 | }, action) => { 15 | switch(action.type) { 16 | case GET_USERS_SUCESS: 17 | return {...state, fetched: true, users: action.users} 18 | case GET_USERS_FAIL: 19 | return {...state, error: action.error} 20 | } 21 | return state; 22 | }; 23 | 24 | export default userReducer; -------------------------------------------------------------------------------- /redux-async-demo/src/router.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { HashRouter, Route } from 'react-router-dom'; 3 | 4 | import App from './components'; 5 | import UserList from './components/UserList'; 6 | import PostList from './components/PostList'; 7 | 8 | const AppRouter = ({dispatch}) => ( 9 | 10 | 11 | 12 | 13 |

路由测试

}>
14 |

路由测试

}>
15 |
16 |
17 | ); 18 | 19 | export default AppRouter; -------------------------------------------------------------------------------- /redux-async-demo/src/sagas/index.jsx: -------------------------------------------------------------------------------- 1 | import { takeEvery, takeLatest } from 'redux-saga'; 2 | import { call, put } from 'redux-saga/effects'; 3 | import axios from 'axios'; 4 | import { BEGIN_GET_POSTS, GET_POSTS, GET_POSTS_ERROR } from '../reducers'; 5 | 6 | // worker saga 7 | function* showPostsAsync(action) { 8 | try { 9 | const response = yield call(axios.get, 'https://jsonplaceholder.typicode.com/posts'); 10 | yield put(GET_POSTS(response.data)); 11 | } catch(e) { 12 | yield put(GET_ERROR(e)); 13 | } 14 | } 15 | 16 | // wacther saga 17 | function* watchGetPosts() { 18 | yield takeLatest(BEGIN_GET_POSTS, showPostsAsync); 19 | } 20 | 21 | // root saga 22 | export default function* rootSaga() { 23 | yield watchGetPosts() 24 | } -------------------------------------------------------------------------------- /redux-router-v4-demo/src/sagas/index.jsx: -------------------------------------------------------------------------------- 1 | import { takeEvery, takeLatest } from 'redux-saga'; 2 | import { call, put } from 'redux-saga/effects'; 3 | import axios from 'axios'; 4 | import { BEGIN_GET_POSTS, GET_POSTS, GET_POSTS_ERROR } from '../reducers'; 5 | 6 | // worker saga 7 | function* showPostsAsync(action) { 8 | try { 9 | const response = yield call(axios.get, 'https://jsonplaceholder.typicode.com/posts'); 10 | yield put(GET_POSTS(response.data)); 11 | } catch(e) { 12 | yield put(GET_ERROR(e)); 13 | } 14 | } 15 | 16 | // wacther saga 17 | function* watchGetPosts() { 18 | yield takeLatest(BEGIN_GET_POSTS, showPostsAsync); 19 | } 20 | 21 | // root saga 22 | export default function* rootSaga() { 23 | yield watchGetPosts() 24 | } -------------------------------------------------------------------------------- /03_redux-router-v4-optimize/src/redux/sagas/index.jsx: -------------------------------------------------------------------------------- 1 | import { takeEvery, takeLatest } from 'redux-saga'; 2 | import { call, put } from 'redux-saga/effects'; 3 | import axios from 'axios'; 4 | 5 | import { 6 | GET_POSTS_SAGA, 7 | GET_POSTS_SUCCESS, 8 | GET_POSTS_FAIL 9 | } from 'constant/actionTypes'; 10 | 11 | import { 12 | GET_POSTS_URL 13 | } from 'constant/url'; 14 | 15 | // worker saga 16 | function* showPostsAsync(action) { 17 | try { 18 | const response = yield call(axios.get, GET_POSTS_URL); 19 | yield put({ type: GET_POSTS_SUCCESS, posts: response.data }); 20 | } catch(e) { 21 | yield put({ type: GET_POSTS_FAIL, error: e }); 22 | } 23 | } 24 | 25 | // wacther saga 26 | function* watchGetPosts() { 27 | yield takeLatest(GET_POSTS_SAGA, showPostsAsync); 28 | } 29 | 30 | // root saga 31 | export default function* rootSaga() { 32 | yield watchGetPosts() 33 | } -------------------------------------------------------------------------------- /03_redux-router-v4-optimize/src/app.jsx: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import '../asset/css/style.scss'; 4 | import 'antd/dist/antd.min.css'; 5 | import React from 'react'; 6 | import { render } from 'react-dom'; 7 | import { Provider } from 'react-redux'; 8 | import { createStore, applyMiddleware } from 'redux'; 9 | import logger from 'redux-logger'; 10 | import thunk from 'redux-thunk'; 11 | import createSagaMiddleware from 'redux-saga'; 12 | import axios from 'axios'; 13 | 14 | import App from './container'; 15 | import appReducer from './redux/reducer'; 16 | import rootSaga from './redux/sagas'; 17 | 18 | const sagaMiddleware = createSagaMiddleware(); 19 | const middlewares = [thunk, sagaMiddleware, logger]; 20 | 21 | const store = createStore(appReducer, applyMiddleware(...middlewares)); 22 | sagaMiddleware.run(rootSaga); 23 | 24 | render( 25 | 26 | 27 | , 28 | document.getElementById('app') 29 | ); -------------------------------------------------------------------------------- /redux-async-demo/src/app.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import '../asset/css/style.scss'; 4 | import 'antd/dist/antd.min.css'; 5 | import React from 'react'; 6 | import { render } from 'react-dom'; 7 | import { Provider } from 'react-redux'; 8 | import { createStore, applyMiddleware, combineReducers } from 'redux'; 9 | import logger from 'redux-logger'; 10 | import thunk from 'redux-thunk'; 11 | import createSagaMiddleware from 'redux-saga'; 12 | import axios from 'axios'; 13 | 14 | import appReducer from './reducers'; 15 | import AppRouter from './router'; 16 | import rootSaga from './sagas'; 17 | 18 | const sagaMiddleware = createSagaMiddleware(); 19 | const middlewares = [thunk, sagaMiddleware, logger]; 20 | 21 | const store = createStore(appReducer, applyMiddleware(...middlewares)); 22 | sagaMiddleware.run(rootSaga); 23 | 24 | render( 25 | 26 | 27 | , 28 | document.getElementById('app') 29 | ); -------------------------------------------------------------------------------- /redux-router-v4-demo/src/app.jsx: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import '../asset/css/style.scss'; 4 | import 'antd/dist/antd.min.css'; 5 | import React from 'react'; 6 | import { render } from 'react-dom'; 7 | import { Provider } from 'react-redux'; 8 | import { createStore, applyMiddleware, combineReducers } from 'redux'; 9 | import logger from 'redux-logger'; 10 | import thunk from 'redux-thunk'; 11 | import createSagaMiddleware from 'redux-saga'; 12 | import axios from 'axios'; 13 | 14 | import appReducer from './reducers'; 15 | import App from './components'; 16 | import rootSaga from './sagas'; 17 | 18 | const sagaMiddleware = createSagaMiddleware(); 19 | const middlewares = [thunk, sagaMiddleware, logger]; 20 | 21 | const store = createStore(appReducer, applyMiddleware(...middlewares)); 22 | sagaMiddleware.run(rootSaga); 23 | 24 | render( 25 | 26 | 27 | , 28 | document.getElementById('app') 29 | ); -------------------------------------------------------------------------------- /redux-async-demo/README.md: -------------------------------------------------------------------------------- 1 | 最近在研究 redux 异步流,看了一些文章,都只是理论上的理解,自己动手实践一遍,才能理解地更深刻~~~ 2 | 3 | #### 用到的技术(-> package.json): 4 | 5 | "antd": "^2.12.8", 6 | "axios": "^0.16.2", 7 | "react": "^15.3.2", 8 | "react-dom": "^15.3.2", 9 | "react-redux": "^5.0.5", 10 | "react-router-dom": "^4.1.1", 11 | "redux": "^3.7.2", 12 | "redux-logger": "^3.0.6", 13 | "redux-saga": "^0.15.3", 14 | "redux-thunk": "^2.2.0" 15 | 16 | #### 主要实现的功能: 17 | 18 | 1. 用 `react-router4` 实现路由切换 19 | 2. `redux-thunk` 异步加载数据 20 | 3. `redux-saga` 异步加载数据 21 | 22 | #### 参考: 23 | 24 | 1) webpack-react 脚手架采用了之前的demo:[react\_webpack\\_scaffold](https://github.com/RukiQ/scaffoldsForFE/tree/master/react_webpack_scaffold) 25 | 26 | 2)页面样式参考了 [react-antd-demo](https://github.com/luckykun/About-React/tree/master/react-antd-demo) 27 | 28 | 3)[react router 4](https://reacttraining.com/react-router/web/example/basic) 29 | 30 | 4)对 redux 异步流的详细介绍请参考我的博文:[聊一聊 redux 异步流之 redux-saga](http://www.jianshu.com/p/e84493c7af35) 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /03_redux-router-v4-optimize/src/container/UserList.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import axios from 'axios'; 3 | 4 | import UserList from 'component/UserList'; 5 | 6 | import { 7 | GET_USERS_SUCESS, 8 | GET_USERS_FAIL 9 | } from 'constant/actionTypes'; 10 | 11 | import { 12 | GET_USERS_URL 13 | } from 'constant/url'; 14 | 15 | const mapStateToProps = (state) => ({ 16 | // users: state.users // 合并的reducer 17 | users: state.users.users // 单独的reducer 18 | }); 19 | 20 | const mapDispatchToProps = (dispatch) => ({ 21 | fetchUsers: () => { 22 | dispatch(() => { 23 | axios.get(GET_USERS_URL) 24 | .then((response) => { 25 | dispatch({ type: GET_USERS_SUCESS, users: response.data }) 26 | }) 27 | .catch((error) => { 28 | dispatch({ type: GET_USERS_FAIL, error }) 29 | }) 30 | }) 31 | } 32 | }); 33 | 34 | export default connect(mapStateToProps, mapDispatchToProps)(UserList); -------------------------------------------------------------------------------- /03_redux-router-v4-optimize/src/component/PostList/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | const { func } = PropTypes; 4 | import { Table } from 'antd'; 5 | 6 | class PostList extends Component { 7 | static propTypes = { 8 | fetchPosts: func 9 | } 10 | 11 | static defaultProps = { 12 | posts: [] 13 | } 14 | 15 | state = { 16 | 17 | }; 18 | 19 | componentWillMount() { 20 | this.props.fetchPosts(); 21 | } 22 | 23 | render() { 24 | const { posts } = this.props; 25 | 26 | const columns = [{ 27 | title: '用户编号', 28 | dataIndex: 'id', 29 | key: 'id', 30 | }, { 31 | title: '标题', 32 | dataIndex: 'title', 33 | key: 'title', 34 | }]; 35 | 36 | return ( 37 |
38 | 39 | 40 | ); 41 | } 42 | } 43 | 44 | export default PostList; -------------------------------------------------------------------------------- /redux-async-demo/src/components/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Link, Route } from 'react-router-dom'; 3 | 4 | import Sider from './Sider'; 5 | 6 | import { Menu, Icon } from 'antd'; 7 | const SubMenu = Menu.SubMenu; 8 | 9 | class App extends Component { 10 | constructor(props) { 11 | super(props); 12 | this.state = {}; 13 | } 14 | 15 | render() { 16 | return ( 17 | 30 | ); 31 | } 32 | } 33 | 34 | export default App; -------------------------------------------------------------------------------- /redux-router-v4-demo/README.md: -------------------------------------------------------------------------------- 1 | 本 demo 在 redux-async-demo 的基础上对路由进行了更改~ 2 | 3 | #### 用到的技术(-> package.json): 4 | 5 | "antd": "^2.12.8", 6 | "axios": "^0.16.2", 7 | "react": "^15.3.2", 8 | "react-dom": "^15.3.2", 9 | "react-redux": "^5.0.5", 10 | "react-router-dom": "^4.1.1", 11 | "redux": "^3.7.2", 12 | "redux-logger": "^3.0.6", 13 | "redux-saga": "^0.15.3", 14 | "redux-thunk": "^2.2.0" 15 | 16 | #### 主要实现的功能: 17 | 18 | 1. 用 `react-router4` 实现路由切换 19 | 2. `redux-thunk` 异步加载数据 20 | 3. `redux-saga` 异步加载数据 21 | 22 | #### 参考: 23 | 24 | 1) webpack-react 脚手架采用了之前的demo:[react\_webpack\\_scaffold](https://github.com/RukiQ/scaffoldsForFE/tree/master/react_webpack_scaffold) 25 | 26 | 2)页面样式参考了 [react-antd-demo](https://github.com/luckykun/About-React/tree/master/react-antd-demo) 27 | 28 | 3)[react router 4](https://reacttraining.com/react-router/web/example/basic) 29 | 30 | 请参考我的博文: [React Router 4:痛过之后的豁然开朗](http://www.jianshu.com/p/bf6b45ce5bcc) 31 | 32 | 4)对 redux 异步流的详细介绍请参考我的博文:[聊一聊 redux 异步流之 redux-saga](http://www.jianshu.com/p/e84493c7af35) 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /redux-async-demo/src/components/PostList/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { connect } from 'react-redux'; 3 | import axios from 'axios'; 4 | import { Begin_GET_POSTS, GET_ERROR } from '../../reducers'; 5 | 6 | import { Table } from 'antd'; 7 | 8 | class PostList extends Component { 9 | constructor(props) { 10 | super(props); 11 | this.state = { 12 | posts: [] 13 | }; 14 | } 15 | 16 | componentWillMount() { 17 | this.props.dispatch(Begin_GET_POSTS()); 18 | } 19 | 20 | render() { 21 | const columns = [{ 22 | title: '用户编号', 23 | dataIndex: 'id', 24 | key: 'id', 25 | }, { 26 | title: '标题', 27 | dataIndex: 'title', 28 | key: 'title', 29 | }]; 30 | 31 | return ( 32 |
33 |
34 | 35 | ); 36 | } 37 | } 38 | 39 | const mapStateToProps = (state) => ({ 40 | posts: state.posts 41 | }); 42 | 43 | export default connect(mapStateToProps)(PostList); -------------------------------------------------------------------------------- /redux-router-v4-demo/src/components/PostList/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { connect } from 'react-redux'; 3 | import axios from 'axios'; 4 | import { Begin_GET_POSTS, GET_ERROR } from '../../reducers'; 5 | 6 | import { Table } from 'antd'; 7 | 8 | class PostList extends Component { 9 | constructor(props) { 10 | super(props); 11 | this.state = { 12 | posts: [] 13 | }; 14 | } 15 | 16 | componentWillMount() { 17 | this.props.dispatch(Begin_GET_POSTS()); 18 | } 19 | 20 | render() { 21 | const columns = [{ 22 | title: '用户编号', 23 | dataIndex: 'id', 24 | key: 'id', 25 | }, { 26 | title: '标题', 27 | dataIndex: 'title', 28 | key: 'title', 29 | }]; 30 | 31 | return ( 32 |
33 |
34 | 35 | ); 36 | } 37 | } 38 | 39 | const mapStateToProps = (state) => ({ 40 | posts: state.posts 41 | }); 42 | 43 | export default connect(mapStateToProps)(PostList); -------------------------------------------------------------------------------- /03_redux-router-v4-optimize/src/component/UserList/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | const { func } = PropTypes; 4 | 5 | import { Table } from 'antd'; 6 | 7 | class UserList extends Component { 8 | static propTypes = { 9 | fetchUsers: func 10 | } 11 | 12 | static defaultProps = { 13 | users: [] 14 | } 15 | 16 | state = { 17 | 18 | } 19 | 20 | componentWillMount() { 21 | this.props.fetchUsers(); 22 | } 23 | 24 | render() { 25 | const { users } = this.props; 26 | 27 | const columns = [{ 28 | title: '姓名', 29 | dataIndex: 'name', 30 | key: 'name', 31 | }, { 32 | title: '邮箱', 33 | dataIndex: 'email', 34 | key: 'email', 35 | }, { 36 | title: '联系方式', 37 | dataIndex: 'phone', 38 | key: 'phone', 39 | }]; 40 | 41 | return ( 42 |
43 |
44 | 45 | ); 46 | } 47 | } 48 | 49 | export default UserList; -------------------------------------------------------------------------------- /redux-async-demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react_webpack_demo", 3 | "version": "1.0.0", 4 | "description": "a demo using react and webpack", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "webpack-dev-server --port 8080 --hot --inline --progress --colors --devtool eval" 8 | }, 9 | "author": "Ruth", 10 | "license": "ISC", 11 | "devDependencies": { 12 | "babel-core": "^6.18.2", 13 | "babel-loader": "^6.2.7", 14 | "babel-plugin-import": "^1.4.0", 15 | "babel-plugin-transform-runtime": "^6.23.0", 16 | "babel-preset-es2015": "^6.18.0", 17 | "babel-preset-react": "^6.16.0", 18 | "babel-preset-stage-0": "^6.24.1", 19 | "css-loader": "^0.25.0", 20 | "extract-text-webpack-plugin": "^1.0.1", 21 | "node-sass": "^4.5.3", 22 | "sass-loader": "^4.0.2", 23 | "style-loader": "^0.13.1", 24 | "webpack": "^1.13.3", 25 | "webpack-dev-server": "^1.16.2" 26 | }, 27 | "dependencies": { 28 | "antd": "^2.12.8", 29 | "axios": "^0.16.2", 30 | "react": "^15.3.2", 31 | "react-dom": "^15.3.2", 32 | "react-redux": "^5.0.5", 33 | "react-router-dom": "^4.1.1", 34 | "redux": "^3.7.2", 35 | "redux-logger": "^3.0.6", 36 | "redux-saga": "^0.15.3", 37 | "redux-thunk": "^2.2.0" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /redux-router-v4-demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react_webpack_demo", 3 | "version": "1.0.0", 4 | "description": "a demo using react and webpack", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "webpack-dev-server --port 8080 --hot --inline --progress --colors --devtool eval" 8 | }, 9 | "author": "Ruth", 10 | "license": "ISC", 11 | "devDependencies": { 12 | "babel-core": "^6.18.2", 13 | "babel-loader": "^6.2.7", 14 | "babel-plugin-import": "^1.4.0", 15 | "babel-plugin-transform-runtime": "^6.23.0", 16 | "babel-preset-es2015": "^6.18.0", 17 | "babel-preset-react": "^6.16.0", 18 | "babel-preset-stage-0": "^6.24.1", 19 | "css-loader": "^0.25.0", 20 | "extract-text-webpack-plugin": "^1.0.1", 21 | "node-sass": "^4.5.3", 22 | "sass-loader": "^4.0.2", 23 | "style-loader": "^0.13.1", 24 | "webpack": "^1.13.3", 25 | "webpack-dev-server": "^1.16.2" 26 | }, 27 | "dependencies": { 28 | "antd": "^2.12.8", 29 | "axios": "^0.16.2", 30 | "react": "^15.3.2", 31 | "react-dom": "^15.3.2", 32 | "react-redux": "^5.0.5", 33 | "react-router-dom": "^4.1.1", 34 | "redux": "^3.7.2", 35 | "redux-logger": "^3.0.6", 36 | "redux-saga": "^0.15.3", 37 | "redux-thunk": "^2.2.0" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /03_redux-router-v4-optimize/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react_webpack_demo", 3 | "version": "1.0.0", 4 | "description": "a demo using react and webpack", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "webpack-dev-server --port 8080 --hot --inline --progress --colors --devtool eval" 8 | }, 9 | "author": "Ruth", 10 | "license": "ISC", 11 | "devDependencies": { 12 | "babel-core": "^6.18.2", 13 | "babel-loader": "^6.2.7", 14 | "babel-plugin-import": "^1.4.0", 15 | "babel-plugin-transform-runtime": "^6.23.0", 16 | "babel-preset-es2015": "^6.18.0", 17 | "babel-preset-react": "^6.16.0", 18 | "babel-preset-stage-0": "^6.24.1", 19 | "css-loader": "^0.25.0", 20 | "extract-text-webpack-plugin": "^1.0.1", 21 | "node-sass": "^4.5.3", 22 | "sass-loader": "^4.0.2", 23 | "style-loader": "^0.13.1", 24 | "webpack": "^1.13.3", 25 | "webpack-dev-server": "^1.16.2" 26 | }, 27 | "dependencies": { 28 | "antd": "^2.12.8", 29 | "axios": "^0.16.2", 30 | "react": "^15.3.2", 31 | "react-dom": "^15.3.2", 32 | "react-redux": "^5.0.5", 33 | "react-router-dom": "^4.1.1", 34 | "redux": "^3.7.2", 35 | "redux-logger": "^3.0.6", 36 | "redux-saga": "^0.15.3", 37 | "redux-thunk": "^2.2.0" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /redux-async-demo/asset/css/style.scss: -------------------------------------------------------------------------------- 1 | *{ 2 | margin: 0; 3 | padding: 0; 4 | } 5 | 6 | html, body { 7 | font-size: 14px; 8 | } 9 | 10 | #nav { 11 | /* 左侧导航固定宽度 */ 12 | #leftMenu { 13 | position: absolute; 14 | top: 0; 15 | left: 0; 16 | bottom: 0; 17 | background: #333; 18 | width: 200px; 19 | box-sizing: border-box; 20 | 21 | .logo { 22 | line-height: 60px; 23 | font-size: 18px; 24 | color: white; 25 | } 26 | } 27 | 28 | /* 右侧宽度自适应 */ 29 | #rightWrap { 30 | -webkit-box-sizing: border-box; 31 | -moz-box-sizing: border-box; 32 | box-sizing: border-box; 33 | padding: 20px 20px 0 20px; 34 | position: absolute; 35 | top: 0; 36 | left: 200px; 37 | right: 0; 38 | bottom: 0; 39 | overflow-y: auto; 40 | 41 | .right-box { 42 | box-sizing: border-box; 43 | padding: 20px 20px 0; 44 | position: absolute; 45 | top: 73px; 46 | left: 0; 47 | right: 0; 48 | bottom: 0; 49 | overflow-y: auto; 50 | } 51 | 52 | .ant-menu-submenu { 53 | float: right; 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /03_redux-router-v4-optimize/src/redux/reducer/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux' 2 | 3 | import users from './users'; 4 | import posts from './posts'; 5 | 6 | const appReducer = (() => combineReducers({ users, posts }))(); 7 | 8 | // ========================= 单独一个文件的写法 ============================= 9 | /* import { 10 | GET_USERS_SUCESS, 11 | GET_USERS_FAIL, 12 | GET_POSTS_SUCCESS, 13 | GET_POSTS_FAIL 14 | } from 'constant/actionTypes'; 15 | 16 | const initialState = { 17 | fetched: false, 18 | users: [{ 19 | key: '1', 20 | name: '张三', 21 | email: 'zhangsan@126.com' 22 | }], 23 | posts: [{ 24 | key: '1', 25 | id: '1', 26 | title: 'test' 27 | }], 28 | error: null 29 | }; 30 | 31 | const appReducer = (state = initialState, action) => { 32 | switch(action.type) { 33 | case GET_USERS_SUCESS: 34 | return {...state, fetched: true, users: action.users} 35 | case GET_USERS_FAIL: 36 | return {...state, error: action.error} 37 | case GET_POSTS_SUCCESS: 38 | return {...state, fetched: true, posts: action.posts} 39 | case GET_POSTS_FAIL: 40 | return {...state, error: action.error} 41 | } 42 | return state; 43 | } */ 44 | 45 | 46 | export default appReducer -------------------------------------------------------------------------------- /redux-router-v4-demo/asset/css/style.scss: -------------------------------------------------------------------------------- 1 | *{ 2 | margin: 0; 3 | padding: 0; 4 | } 5 | 6 | html, body { 7 | font-size: 14px; 8 | } 9 | 10 | #nav { 11 | /* 左侧导航固定宽度 */ 12 | #leftMenu { 13 | position: absolute; 14 | top: 0; 15 | left: 0; 16 | bottom: 0; 17 | background: #333; 18 | width: 200px; 19 | box-sizing: border-box; 20 | 21 | .logo { 22 | line-height: 60px; 23 | font-size: 18px; 24 | color: white; 25 | } 26 | } 27 | 28 | /* 右侧宽度自适应 */ 29 | #rightWrap { 30 | -webkit-box-sizing: border-box; 31 | -moz-box-sizing: border-box; 32 | box-sizing: border-box; 33 | padding: 20px 20px 0 20px; 34 | position: absolute; 35 | top: 0; 36 | left: 200px; 37 | right: 0; 38 | bottom: 0; 39 | overflow-y: auto; 40 | 41 | .right-box { 42 | box-sizing: border-box; 43 | padding: 20px 20px 0; 44 | position: absolute; 45 | top: 73px; 46 | left: 0; 47 | right: 0; 48 | bottom: 0; 49 | overflow-y: auto; 50 | } 51 | 52 | .ant-menu-submenu { 53 | float: right; 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /03_redux-router-v4-optimize/asset/css/style.scss: -------------------------------------------------------------------------------- 1 | *{ 2 | margin: 0; 3 | padding: 0; 4 | } 5 | 6 | html, body { 7 | font-size: 14px; 8 | } 9 | 10 | #nav { 11 | /* 左侧导航固定宽度 */ 12 | #leftMenu { 13 | position: absolute; 14 | top: 0; 15 | left: 0; 16 | bottom: 0; 17 | background: #333; 18 | width: 200px; 19 | box-sizing: border-box; 20 | 21 | .logo { 22 | line-height: 60px; 23 | font-size: 18px; 24 | color: white; 25 | } 26 | } 27 | 28 | /* 右侧宽度自适应 */ 29 | #rightWrap { 30 | -webkit-box-sizing: border-box; 31 | -moz-box-sizing: border-box; 32 | box-sizing: border-box; 33 | padding: 20px 20px 0 20px; 34 | position: absolute; 35 | top: 0; 36 | left: 200px; 37 | right: 0; 38 | bottom: 0; 39 | overflow-y: auto; 40 | 41 | .right-box { 42 | box-sizing: border-box; 43 | padding: 20px 20px 0; 44 | position: absolute; 45 | top: 73px; 46 | left: 0; 47 | right: 0; 48 | bottom: 0; 49 | overflow-y: auto; 50 | } 51 | 52 | .ant-menu-submenu { 53 | float: right; 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 最近在研究 redux 异步流,看了一些文章,都只是理论上的理解,自己动手实践一遍,才能理解地更深刻~~~ 2 | 3 | ### 一、说明 4 | 5 | **redux-async-demo**:最开始的基础版本 6 | 7 | **redux-router-v4-demo**:在 **redux-async-demo** 的基础上对路由进行了更改 8 | 9 | **03_redux-router-v4-optimize**:在 **redux-router-v4-demo** 的基础上对模块结构进行了优化,分离了业务组件和纯组件,提高组件复用性,模块结构更加清晰。 10 | 11 | ### 二、用到的技术(-> package.json): 12 | 13 | "antd": "^2.12.8", 14 | "axios": "^0.16.2", 15 | "react": "^15.3.2", 16 | "react-dom": "^15.3.2", 17 | "react-redux": "^5.0.5", 18 | "react-router-dom": "^4.1.1", 19 | "redux": "^3.7.2", 20 | "redux-logger": "^3.0.6", 21 | "redux-saga": "^0.15.3", 22 | "redux-thunk": "^2.2.0" 23 | 24 | ### 三、主要实现的功能: 25 | 26 | 1. 用 `react-router4` 实现路由切换 27 | 2. `redux-thunk` 异步加载数据 28 | 3. `redux-saga` 异步加载数据 29 | 30 | ### 四、参考: 31 | 32 | 1) webpack-react 脚手架采用了之前的demo:[react\_webpack\\_scaffold](https://github.com/RukiQ/scaffoldsForFE) 33 | 34 | 2)页面样式参考了 [react-antd-demo](https://github.com/luckykun/About-React/tree/master/react-antd-demo) 35 | 36 | 3)[react router 4](https://reacttraining.com/react-router/web/example/basic) 37 | 38 | 请参考我的博文: [React Router 4:痛过之后的豁然开朗](http://www.jianshu.com/p/bf6b45ce5bcc) 39 | 40 | 4)对 redux 异步流的详细介绍请参考我的博文:[聊一聊 redux 异步流之 redux-saga](http://www.jianshu.com/p/e84493c7af35) 41 | 42 | 5)对项目的梳理请参考我的博文:[React技术栈整套工程化流程](https://www.jianshu.com/p/088116f02b26) 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /redux-router-v4-demo/src/components/Sider/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Link, Route } from 'react-router-dom'; 3 | 4 | // 引入Antd的导航组件 5 | import { Menu, Icon } from 'antd'; 6 | const SubMenu = Menu.SubMenu; 7 | 8 | class Sider extends Component { 9 | constructor(props) { 10 | super(props); 11 | this.state = {}; 12 | } 13 | 14 | render() { 15 | return ( 16 |
17 | 主菜单 18 | 24 | 子菜单}> 25 | 用redux-thunk获取数据 26 | 用redux-saga获取数据 27 | 测试路由 28 | 测试路由 29 | 30 | 31 |
32 | ); 33 | } 34 | } 35 | 36 | export default Sider -------------------------------------------------------------------------------- /03_redux-router-v4-optimize/src/component/Sider/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Link, Route } from 'react-router-dom'; 3 | 4 | // 引入Antd的导航组件 5 | import { Menu, Icon } from 'antd'; 6 | const SubMenu = Menu.SubMenu; 7 | 8 | class Sider extends Component { 9 | constructor(props) { 10 | super(props); 11 | this.state = {}; 12 | } 13 | 14 | render() { 15 | return ( 16 |
17 | 主菜单 18 | 24 | 子菜单}> 25 | 用redux-thunk获取数据 26 | 用redux-saga获取数据 27 | 测试路由 28 | 测试路由 29 | 30 | 31 |
32 | ); 33 | } 34 | } 35 | 36 | export default Sider -------------------------------------------------------------------------------- /redux-async-demo/src/components/Sider/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Link, Route } from 'react-router-dom'; 3 | 4 | // 引入Antd的导航组件 5 | import { Menu, Icon } from 'antd'; 6 | const SubMenu = Menu.SubMenu; 7 | 8 | class Sider extends Component { 9 | constructor(props) { 10 | super(props); 11 | this.state = {}; 12 | } 13 | 14 | render() { 15 | return ( 16 |
17 | 主菜单 18 | 24 | 子菜单}> 25 | 用redux-thunk获取数据 26 | 用redux-saga获取数据 27 | 测试路由 28 | 测试路由 29 | 30 | 31 |
32 | ); 33 | } 34 | } 35 | 36 | export default Sider -------------------------------------------------------------------------------- /03_redux-router-v4-optimize/README.md: -------------------------------------------------------------------------------- 1 | 本 demo 在 redux-router-v4-demo 的基础上对模块结构进行了优化,分离了业务组件和纯组件,提高组件复用性,模块结构更加清晰。 2 | 3 | 具体梳理请参考我的博文:[React技术栈整套工程化流程](https://www.jianshu.com/p/088116f02b26) 4 | 5 | ### 一、目录结构: 6 | 7 | - /asset 8 | - /css 9 | - /img 10 | - /src 11 | - /common --------------- 公用类库 12 | - /component ------------ Dumb 组件 13 | - /PostList 14 | - /Sider 15 | - /UserList 16 | - /constant ------------- 公用常量 17 | - /actionTypes 18 | - /url 19 | - /container ------------ Smart 组件 20 | - index.jsx --------- App 业务组件 21 | - PostList.jsx ------ 采用 Saga 获取数据 22 | - UserList.jsx ------ 采用 Thunk 获取数据 23 | - /redux 24 | - /reducer 25 | - /sagas 26 | - app.jsx ---------------- app入口 27 | - .babelrc 28 | - index.html 29 | - package.json 30 | - README.md 31 | - webpack.config.js 32 | 33 | ### 二、模块间调用关系 34 | 35 | `app.jsx` 为启动入口,配置好 **Thunk/Saga**,将 `store` 传入 ``; 36 | 37 | `` 为 **container** 中的业务组件,专门用来获取异步数据,这样就可以跟 **component** 中的纯组件解耦; 38 | 39 | **component** 中的组件不用关心外部的情况,只需要关注传入的 `props` 进行渲染即可。 40 | 41 | > 每个 Smart 组件跟 Dumb 组件都通过 `connect` 传递 `props`:通过 `mapStateToProps` 传递 state,通过 `mapDispatchToProps` 传递 dispatch。 42 | 43 | #### (1)Thunk 处理流程 44 | 45 | **/container/UserList.js** 46 | 47 | **Thunk** 直接在业务组件中 `dispatch` 一个函数来异步获取数据。 48 | 49 | 50 | #### (2)Saga 处理流程 51 | 52 | **Saga** 在业务组件中 `dispatch` 一个获取数据的 `action` 命令,然后 `saga` 监听到该 `action` 之后再去获取数据。 53 | 54 | --- 55 | 最后,**Thunk** 和 **Saga** 在异步获取数据之后都会再 `dispatch` 一个 `action`,然后,`reducer` 根据原有的 state 和 该 `action` 返回新的 `state`。 56 | -------------------------------------------------------------------------------- /redux-async-demo/src/components/UserList/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { connect } from 'react-redux'; 3 | import axios from 'axios'; 4 | import { GET_USERS, GET_ERROR } from '../../reducers'; 5 | import reducer from '../../reducers'; 6 | 7 | import { Table } from 'antd'; 8 | 9 | class UserList extends Component { 10 | static defaultProps = { 11 | users: null 12 | } 13 | 14 | constructor(props) { 15 | console.log(props); 16 | super(props); 17 | } 18 | 19 | componentWillMount() { 20 | this.props.dispatch((dispatch) => { 21 | axios.get('https://jsonplaceholder.typicode.com/users') 22 | .then((response) => { 23 | dispatch(GET_USERS(response.data)) 24 | }) 25 | .catch((error) => { 26 | dispatch(GET_ERROR(error)) 27 | }) 28 | }); 29 | } 30 | 31 | render() { 32 | const columns = [{ 33 | title: '姓名', 34 | dataIndex: 'name', 35 | key: 'name', 36 | }, { 37 | title: '邮箱', 38 | dataIndex: 'email', 39 | key: 'email', 40 | }, { 41 | title: '联系方式', 42 | dataIndex: 'phone', 43 | key: 'phone', 44 | }]; 45 | 46 | return ( 47 |
48 |
49 | 50 | ); 51 | } 52 | } 53 | 54 | const mapStateToProps = (state) => ({ 55 | users: state.users 56 | }); 57 | 58 | export default connect(mapStateToProps)(UserList); -------------------------------------------------------------------------------- /redux-router-v4-demo/src/components/UserList/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { connect } from 'react-redux'; 3 | import axios from 'axios'; 4 | import { GET_USERS, GET_ERROR } from '../../reducers'; 5 | import reducer from '../../reducers'; 6 | 7 | import { Table } from 'antd'; 8 | 9 | class UserList extends Component { 10 | static defaultProps = { 11 | users: null 12 | } 13 | 14 | constructor(props) { 15 | console.log(props); 16 | super(props); 17 | } 18 | 19 | componentWillMount() { 20 | this.props.dispatch((dispatch) => { 21 | axios.get('https://jsonplaceholder.typicode.com/users') 22 | .then((response) => { 23 | dispatch(GET_USERS(response.data)) 24 | }) 25 | .catch((error) => { 26 | dispatch(GET_ERROR(error)) 27 | }) 28 | }); 29 | } 30 | 31 | render() { 32 | const columns = [{ 33 | title: '姓名', 34 | dataIndex: 'name', 35 | key: 'name', 36 | }, { 37 | title: '邮箱', 38 | dataIndex: 'email', 39 | key: 'email', 40 | }, { 41 | title: '联系方式', 42 | dataIndex: 'phone', 43 | key: 'phone', 44 | }]; 45 | 46 | return ( 47 |
48 |
49 | 50 | ); 51 | } 52 | } 53 | 54 | const mapStateToProps = (state) => ({ 55 | users: state.users 56 | }); 57 | 58 | export default connect(mapStateToProps)(UserList); -------------------------------------------------------------------------------- /redux-async-demo/src/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux' 2 | 3 | // actions 4 | export const RECEIVE_USERS = 'RECEIVE_USERS'; 5 | export const FETCH_USERS_ERROR = 'FETCH_USERS_ERROR'; 6 | export const RECEIVE_POSTS = 'RECEIVE_POPTS'; 7 | export const FETCH_POSTS_ERROR = 'FETCH_USERS_ERROR'; 8 | export const BEGIN_GET_POSTS = 'BEGIN_GET_POSTS'; 9 | 10 | // action creators 11 | export function GET_USERS(users) { 12 | return { type: RECEIVE_USERS, users } 13 | } 14 | 15 | export function GET_ERROR(error) { 16 | return { type: FETCH_USERS_ERROR, error } 17 | } 18 | 19 | export function GET_POSTS(posts) { 20 | return { type: RECEIVE_POSTS, posts } 21 | } 22 | 23 | export function Begin_GET_POSTS() { 24 | return { type: BEGIN_GET_POSTS } 25 | } 26 | 27 | export function GET_POSTS_ERROR(error) { 28 | return { type: FETCH_POSTS_ERROR, error } 29 | } 30 | 31 | // reducer 32 | const initialState = { 33 | fetched: false, 34 | users: [{ 35 | key: '1', 36 | name: '张三', 37 | email: 'zhangsan@126.com' 38 | }], 39 | posts: [{ 40 | key: '1', 41 | id: '1', 42 | title: 'test' 43 | }], 44 | error: null 45 | }; 46 | 47 | const appReducer = (state = initialState, action) => { 48 | switch(action.type) { 49 | case FETCH_USERS_ERROR: { 50 | return {...state, error: action.error} 51 | break; 52 | } 53 | case RECEIVE_USERS: { 54 | return {...state, fetched: true, users: action.users} 55 | break; 56 | } 57 | case FETCH_POSTS_ERROR: { 58 | return {...state, error: action.error} 59 | break; 60 | } 61 | case RECEIVE_POSTS: { 62 | return {...state, fetched: true, posts: action.posts} 63 | break; 64 | } 65 | } 66 | return state; 67 | } 68 | 69 | export default appReducer -------------------------------------------------------------------------------- /redux-router-v4-demo/src/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux' 2 | 3 | // actions 4 | export const RECEIVE_USERS = 'RECEIVE_USERS'; 5 | export const FETCH_USERS_ERROR = 'FETCH_USERS_ERROR'; 6 | export const RECEIVE_POSTS = 'RECEIVE_POPTS'; 7 | export const FETCH_POSTS_ERROR = 'FETCH_USERS_ERROR'; 8 | export const BEGIN_GET_POSTS = 'BEGIN_GET_POSTS'; 9 | 10 | // action creators 11 | export function GET_USERS(users) { 12 | return { type: RECEIVE_USERS, users } 13 | } 14 | 15 | export function GET_ERROR(error) { 16 | return { type: FETCH_USERS_ERROR, error } 17 | } 18 | 19 | export function GET_POSTS(posts) { 20 | return { type: RECEIVE_POSTS, posts } 21 | } 22 | 23 | export function Begin_GET_POSTS() { 24 | return { type: BEGIN_GET_POSTS } 25 | } 26 | 27 | export function GET_POSTS_ERROR(error) { 28 | return { type: FETCH_POSTS_ERROR, error } 29 | } 30 | 31 | // reducer 32 | const initialState = { 33 | fetched: false, 34 | users: [{ 35 | key: '1', 36 | name: '张三', 37 | email: 'zhangsan@126.com' 38 | }], 39 | posts: [{ 40 | key: '1', 41 | id: '1', 42 | title: 'test' 43 | }], 44 | error: null 45 | }; 46 | 47 | const appReducer = (state = initialState, action) => { 48 | switch(action.type) { 49 | case FETCH_USERS_ERROR: { 50 | return {...state, error: action.error} 51 | break; 52 | } 53 | case RECEIVE_USERS: { 54 | return {...state, fetched: true, users: action.users} 55 | break; 56 | } 57 | case FETCH_POSTS_ERROR: { 58 | return {...state, error: action.error} 59 | break; 60 | } 61 | case RECEIVE_POSTS: { 62 | return {...state, fetched: true, posts: action.posts} 63 | break; 64 | } 65 | } 66 | return state; 67 | } 68 | 69 | export default appReducer -------------------------------------------------------------------------------- /redux-async-demo/webpack.config.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: Ruth 3 | * @Date: 2016-11-02 11:25:52 4 | * @Last Modified time: 2016-11-14 15:33:35 5 | */ 6 | 7 | 'use strict'; 8 | 9 | var webpack = require('webpack'); 10 | 11 | // css 单独打包,使用该插件后就不需要配置style-loader了 12 | // 本来是内联在最终的网页里,现在通过外联方式,可以在/dist文件夹下找到单独的css文件 13 | var ExtractTextPlugin = require('extract-text-webpack-plugin'); 14 | 15 | module.exports = { 16 | entry: { 17 | index: './src/app.js', // 唯一的入口文件 18 | vendor: [ // 这里是依赖的库文件配置,和CommonsChunkPlugin配合使用可以单独打包 19 | 'react', 20 | 'react-dom', 21 | 'react-redux', 22 | 'react-router-dom', 23 | 'redux', 24 | 'redux-logger', 25 | 'redux-thunk', 26 | 'redux-saga', 27 | 'axios' 28 | ] 29 | }, 30 | output: { 31 | path: '/dist', //打包后的文件存放的地方 32 | filename: 'bundle.js', 33 | publicPath: 'http://localhost:8080/dist/' //启动本地服务后的根目录 34 | }, 35 | devServer: { 36 | historyApiFallback: true, 37 | hot: true, 38 | inline: true, 39 | progress: true 40 | }, 41 | resolve: { 42 | extensions: ['', '.js', '.jsx'] 43 | }, 44 | module: { 45 | loaders: [{ 46 | test: /\.(js|jsx)$/, 47 | loader: 'babel', 48 | // 可以单独在当前目录下配置.babelrc,也可以在这里配置 49 | query: { 50 | // presets: ['es2015', 'react'] 51 | }, 52 | // 排除 node_modules 下不需要转换的文件,可以加快编译 53 | exclude: /node_modules/ 54 | }, { 55 | test: /\.css$/, 56 | loader: ExtractTextPlugin.extract("style", "css") 57 | }, { 58 | test: /\.scss$/, 59 | loader: ExtractTextPlugin.extract("style", "css!sass") 60 | }, { 61 | test: /\.(png|jpg|gif)$/, 62 | loader: 'url?limit=819200' 63 | }] 64 | }, 65 | plugins: [ 66 | new ExtractTextPlugin('main.css'), 67 | new webpack.optimize.CommonsChunkPlugin({ 68 | name: 'vendor', 69 | filename: 'vendor.js' 70 | }) 71 | ] 72 | }; -------------------------------------------------------------------------------- /redux-router-v4-demo/webpack.config.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: Ruth 3 | * @Date: 2016-11-02 11:25:52 4 | * @Last Modified time: 2016-11-14 15:33:35 5 | */ 6 | 7 | 'use strict'; 8 | 9 | var webpack = require('webpack'); 10 | 11 | // css 单独打包,使用该插件后就不需要配置style-loader了 12 | // 本来是内联在最终的网页里,现在通过外联方式,可以在/dist文件夹下找到单独的css文件 13 | var ExtractTextPlugin = require('extract-text-webpack-plugin'); 14 | 15 | module.exports = { 16 | entry: { 17 | index: './src/app.jsx', // 唯一的入口文件 18 | vendor: [ // 这里是依赖的库文件配置,和CommonsChunkPlugin配合使用可以单独打包 19 | 'react', 20 | 'react-dom', 21 | 'react-redux', 22 | 'react-router-dom', 23 | 'redux', 24 | 'redux-logger', 25 | 'redux-thunk', 26 | 'redux-saga', 27 | 'axios' 28 | ] 29 | }, 30 | output: { 31 | path: '/dist', //打包后的文件存放的地方 32 | filename: 'bundle.js', 33 | publicPath: 'http://localhost:8080/dist/' //启动本地服务后的根目录 34 | }, 35 | devServer: { 36 | historyApiFallback: true, 37 | hot: true, 38 | inline: true, 39 | progress: true 40 | }, 41 | resolve: { 42 | extensions: ['', '.js', '.jsx'] 43 | }, 44 | module: { 45 | loaders: [{ 46 | test: /\.(js|jsx)$/, 47 | loader: 'babel', 48 | // 可以单独在当前目录下配置.babelrc,也可以在这里配置 49 | query: { 50 | // presets: ['es2015', 'react'] 51 | }, 52 | // 排除 node_modules 下不需要转换的文件,可以加快编译 53 | exclude: /node_modules/ 54 | }, { 55 | test: /\.css$/, 56 | loader: ExtractTextPlugin.extract("style", "css") 57 | }, { 58 | test: /\.scss$/, 59 | loader: ExtractTextPlugin.extract("style", "css!sass") 60 | }, { 61 | test: /\.(png|jpg|gif)$/, 62 | loader: 'url?limit=819200' 63 | }] 64 | }, 65 | plugins: [ 66 | new ExtractTextPlugin('main.css'), 67 | new webpack.optimize.CommonsChunkPlugin({ 68 | name: 'vendor', 69 | filename: 'vendor.js' 70 | }) 71 | ] 72 | }; -------------------------------------------------------------------------------- /03_redux-router-v4-optimize/webpack.config.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: Ruth 3 | * @Date: 2016-11-02 11:25:52 4 | * @Last Modified time: 2016-11-14 15:33:35 5 | */ 6 | 7 | 'use strict'; 8 | 9 | var webpack = require('webpack'); 10 | var path = require('path'); 11 | var resolve = path.resolve; 12 | 13 | // css 单独打包,使用该插件后就不需要配置style-loader了 14 | // 本来是内联在最终的网页里,现在通过外联方式,可以在/dist文件夹下找到单独的css文件 15 | var ExtractTextPlugin = require('extract-text-webpack-plugin'); 16 | 17 | module.exports = { 18 | entry: { 19 | index: './src/app.jsx', // 唯一的入口文件 20 | vendor: [ // 这里是依赖的库文件配置,和CommonsChunkPlugin配合使用可以单独打包 21 | 'react', 22 | 'react-dom', 23 | 'react-redux', 24 | 'react-router-dom', 25 | 'redux', 26 | 'redux-logger', 27 | 'redux-thunk', 28 | 'redux-saga', 29 | 'axios' 30 | ] 31 | }, 32 | output: { 33 | path: '/dist', //打包后的文件存放的地方 34 | filename: 'bundle.js', 35 | publicPath: '/dist' //启动本地服务后的根目录 36 | }, 37 | devServer: { 38 | historyApiFallback: true, 39 | hot: true, 40 | inline: true, 41 | progress: true 42 | }, 43 | resolve: { 44 | extensions: ['', '.js', '.jsx'], 45 | alias: { 46 | 'common': resolve('src/common'), 47 | 'component': resolve('src/component'), 48 | 'container': resolve('src/container'), 49 | 'asset': resolve('asset'), 50 | 'constant': resolve('src/constant') 51 | } 52 | }, 53 | module: { 54 | loaders: [{ 55 | test: /\.(js|jsx)$/, 56 | loader: 'babel', 57 | // 可以单独在当前目录下配置.babelrc,也可以在这里配置 58 | query: { 59 | // presets: ['es2015', 'react'] 60 | }, 61 | // 排除 node_modules 下不需要转换的文件,可以加快编译 62 | exclude: /node_modules/ 63 | }, { 64 | test: /\.css$/, 65 | loader: ExtractTextPlugin.extract("style", "css") 66 | }, { 67 | test: /\.scss$/, 68 | loader: ExtractTextPlugin.extract("style", "css!sass") 69 | }, { 70 | test: /\.(png|jpg|gif)$/, 71 | loader: 'url?limit=819200' 72 | }] 73 | }, 74 | plugins: [ 75 | new ExtractTextPlugin('main.css'), 76 | new webpack.optimize.CommonsChunkPlugin({ 77 | name: 'vendor', 78 | filename: 'vendor.js' 79 | }) 80 | ] 81 | }; -------------------------------------------------------------------------------- /03_redux-router-v4-optimize/src/container/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { HashRouter, Switch, Route, Redirect, Link } from 'react-router-dom'; 3 | 4 | import Sider from 'component/Sider'; 5 | import UserList from './UserList'; 6 | import PostList from './PostList'; 7 | 8 | import { Menu, Icon } from 'antd'; 9 | const SubMenu = Menu.SubMenu; 10 | 11 | class App extends Component { 12 | constructor(props) { 13 | super(props); 14 | this.state = {}; 15 | } 16 | 17 | render() { 18 | return ( 19 | 20 | 41 | 42 | ); 43 | } 44 | } 45 | 46 | /* const UserSubLayout = (props) => { 47 | return ( 48 |
49 |

请参考博文: React Router 4:痛过之后的豁然开朗

50 | 53 |
54 | 55 | 56 | 57 | 58 |
59 |
60 | ) 61 | } 62 | 63 | 64 | const UserComments = ({ match }) => { 65 | return
UserId: {match.params.userId}
66 | } 67 | 68 | const UserSettings = ({ match }) => { 69 | return
UserId: {match.params.userId}
70 | } 71 | 72 | const UserProfilePage = ({ match }) => { 73 | return ( 74 |
75 | User Profile: 76 | 77 | 78 |
79 | ) 80 | } 81 | 82 | 83 | const UserNav = () => ( 84 |
User Nav
85 | ) 86 | 87 | const BrowseUserTable = ({ match }) => { 88 | return ( 89 | 93 | ) 94 | } 95 | 96 | const UserProfile = ({ userId }) =>
User: {userId}
; */ 97 | 98 | export default App; -------------------------------------------------------------------------------- /redux-router-v4-demo/src/components/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { HashRouter, Switch, Route, Redirect, Link } from 'react-router-dom'; 3 | 4 | import Sider from './Sider'; 5 | import UserList from './UserList'; 6 | import PostList from './PostList'; 7 | 8 | import { Menu, Icon } from 'antd'; 9 | const SubMenu = Menu.SubMenu; 10 | 11 | class App extends Component { 12 | constructor(props) { 13 | super(props); 14 | this.state = {}; 15 | } 16 | 17 | render() { 18 | return ( 19 | 20 | 43 | 44 | ); 45 | } 46 | } 47 | 48 | const UserSubLayout = (props) => { 49 | return ( 50 |
51 |

请参考博文: React Router 4:痛过之后的豁然开朗

52 | 55 |
56 | 57 | 58 | 59 | 60 |
61 |
62 | ) 63 | } 64 | 65 | /* const UserProfilePage = ({match}) => { 66 | console.log(match.path); // output: "/users/:userId" 67 | console.log(match.url); // output: "/users/bob" 68 | return 69 | } */ 70 | 71 | const UserComments = ({ match }) => { 72 | //console.log(match.params); // output: {} 73 | return
UserId: {match.params.userId}
74 | } 75 | 76 | const UserSettings = ({ match }) => { 77 | //console.log(match.params); // output: {userId: "5"} 78 | return
UserId: {match.params.userId}
79 | } 80 | 81 | const UserProfilePage = ({ match }) => { 82 | // console.log(match) 83 | return ( 84 |
85 | User Profile: 86 | 87 | 88 |
89 | ) 90 | } 91 | 92 | 93 | /* const BrowseUsersPage = () => ( 94 |
95 | 98 |
99 | 100 |
101 |
102 | ) 103 | 104 | 105 | const UserProfilePage = props => ( 106 |
107 | 110 |
111 | 112 |
113 |
114 | ) */ 115 | 116 | const UserNav = () => ( 117 |
User Nav
118 | ) 119 | 120 | const BrowseUserTable = ({ match }) => { 121 | return ( 122 | 126 | ) 127 | } 128 | 129 | const UserProfile = ({ userId }) =>
User: {userId}
; 130 | 131 | export default App; --------------------------------------------------------------------------------