├── 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 |
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 |
18 |
19 |
20 |
25 |
26 | {this.props.children}
27 |
28 |
29 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
21 |
22 |
23 |
28 |
29 |
30 | Home Page
} />
31 |
32 |
33 | 路由测试
}>
34 | 路由测试
}>
35 | {/* */}
36 |
37 |
38 |
39 |
40 |
41 |
42 | );
43 | }
44 | }
45 |
46 | /* const UserSubLayout = (props) => {
47 | return (
48 |
49 |
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 |
90 | - comments
91 | - settings
92 |
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 |
21 |
22 |
23 |
28 |
29 |
30 | Home Page
} />
31 |
32 |
33 | 路由测试
}>
34 | 路由测试
}>
35 | {/*
36 | */}
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | );
45 | }
46 | }
47 |
48 | const UserSubLayout = (props) => {
49 | return (
50 |
51 |
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 |
123 | - comments
124 | - settings
125 |
126 | )
127 | }
128 |
129 | const UserProfile = ({ userId }) => User: {userId}
;
130 |
131 | export default App;
--------------------------------------------------------------------------------