├── .gitignore ├── LICENSE ├── README.md ├── package-lock.json ├── package.json ├── public ├── favicon.ico ├── index.html └── manifest.json ├── screenshot ├── React+Redux Cycle.png ├── UI2container.png ├── code-splitting.png ├── combine-reducer.png ├── container2container.png ├── react-router.png ├── react-stack.png └── redux-saga.png └── src ├── App.css ├── App.js ├── actions ├── addInputAction.js ├── counterAction.js ├── fetchAction.js └── fetchAsyncAction.js ├── components ├── Fu.js ├── GridCharts.js ├── MoreWiseCharts.js ├── SearchResults.js ├── SideMeu.js ├── WiseCharts.1.js ├── WiseCharts.css ├── WiseCharts.js ├── WiseCharts1.js └── Zi.js ├── containers ├── AddInput.js ├── DevTools.js ├── GetList.js ├── NumCard.js └── SagaAsy.js ├── index.css ├── index.js ├── reducers ├── addInput.js ├── counter.js ├── sagaReducer.js └── thunkReducer.js ├── registerServiceWorker.js ├── sagas └── sagas.js └── store.js /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 沉良 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-r3-saga 2 | 可能是最简单React+Redux+Router+Redux-saga+AntD+ES6全家桶 3 | 4 | ## 运行项目(nodejs 6.0+) 5 | 6 | ``` 7 | git clone https://github.com/wlc534/react-r3-saga 8 | 9 | cd react-r3-saga 10 | 11 | npm install 12 | 13 | npm start 14 | 15 | 16 | ``` 17 | ## 说明 18 | 19 | > 本项目主要用于学习 react、redux、router、redux-thunk、redux-saga、AntD等从UI到数据状态管理整合 20 | 21 | > 如果觉得不错的话,您可以点右上角 "Star" 支持一下 谢谢! ^_^ 22 | 23 | > 或者您可以 "follow" 一下,我会不断开源更多的有趣的项目 24 | 25 | > 如有问题请直接在 Issues 中提,或者您发现问题并有非常好的解决方案,欢迎 PR 👍 26 | 27 | > 开发环境 Windows 10 Chrome 67 nodejs v8.11.3 28 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-r3-saga", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "antd": "^3.6.5", 7 | "axios": "^0.19.0", 8 | "echarts": "^4.1.0", 9 | "echarts-for-react": "^2.0.14", 10 | "lodash": "^4.17.11", 11 | "react": "^16.8.6", 12 | "react-dom": "^16.8.6", 13 | "react-grid-layout": "^0.16.6", 14 | "react-loadable": "^5.4.0", 15 | "react-redux": "^5.0.7", 16 | "react-router-dom": "^4.3.1", 17 | "react-scripts": "1.1.4", 18 | "redux": "^4.0.0", 19 | "redux-logger": "^3.0.6", 20 | "redux-saga": "^0.16.0", 21 | "redux-slider-monitor": "^2.0.0-1", 22 | "redux-thunk": "^2.3.0" 23 | }, 24 | "scripts": { 25 | "start": "react-scripts start", 26 | "build": "react-scripts build", 27 | "test": "react-scripts test --env=jsdom", 28 | "eject": "react-scripts eject" 29 | }, 30 | "devDependencies": { 31 | "install": "^0.12.1", 32 | "redux-devtools": "^3.4.1", 33 | "redux-devtools-chart-monitor": "^1.7.0", 34 | "redux-devtools-dock-monitor": "^1.1.3", 35 | "redux-devtools-extension": "^2.13.5", 36 | "redux-devtools-filterable-log-monitor": "^0.8.0", 37 | "redux-devtools-inspector": "^0.11.3", 38 | "redux-devtools-log-monitor": "^1.4.0" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wlc534/react-r3-saga/18f34ce6f1d5dffb8b3b1d5f2c6c4e846928afee/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 22 | React App 23 | 24 | 25 | 28 |
29 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /screenshot/React+Redux Cycle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wlc534/react-r3-saga/18f34ce6f1d5dffb8b3b1d5f2c6c4e846928afee/screenshot/React+Redux Cycle.png -------------------------------------------------------------------------------- /screenshot/UI2container.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wlc534/react-r3-saga/18f34ce6f1d5dffb8b3b1d5f2c6c4e846928afee/screenshot/UI2container.png -------------------------------------------------------------------------------- /screenshot/code-splitting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wlc534/react-r3-saga/18f34ce6f1d5dffb8b3b1d5f2c6c4e846928afee/screenshot/code-splitting.png -------------------------------------------------------------------------------- /screenshot/combine-reducer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wlc534/react-r3-saga/18f34ce6f1d5dffb8b3b1d5f2c6c4e846928afee/screenshot/combine-reducer.png -------------------------------------------------------------------------------- /screenshot/container2container.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wlc534/react-r3-saga/18f34ce6f1d5dffb8b3b1d5f2c6c4e846928afee/screenshot/container2container.png -------------------------------------------------------------------------------- /screenshot/react-router.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wlc534/react-r3-saga/18f34ce6f1d5dffb8b3b1d5f2c6c4e846928afee/screenshot/react-router.png -------------------------------------------------------------------------------- /screenshot/react-stack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wlc534/react-r3-saga/18f34ce6f1d5dffb8b3b1d5f2c6c4e846928afee/screenshot/react-stack.png -------------------------------------------------------------------------------- /screenshot/redux-saga.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wlc534/react-r3-saga/18f34ce6f1d5dffb8b3b1d5f2c6c4e846928afee/screenshot/redux-saga.png -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | @import '~antd/dist/antd.css'; 2 | .App { 3 | text-align: center; 4 | display: flex; 5 | justify-content: center; 6 | } 7 | .flexP{ 8 | margin-top: 16px; 9 | display: flex; 10 | justify-content: space-around; 11 | } 12 | .numShow{ 13 | text-align: center; 14 | font-size: 10em; 15 | line-height: 200px; 16 | height: 200px; 17 | background: aliceblue; 18 | } 19 | 20 | .App-logo { 21 | animation: App-logo-spin infinite 20s linear; 22 | height: 80px; 23 | } 24 | 25 | .App-header { 26 | background-color: #222; 27 | height: 150px; 28 | padding: 20px; 29 | color: white; 30 | } 31 | 32 | .App-title { 33 | font-size: 1.5em; 34 | } 35 | 36 | .App-intro { 37 | font-size: large; 38 | } 39 | 40 | @keyframes App-logo-spin { 41 | from { transform: rotate(0deg); } 42 | to { transform: rotate(360deg); } 43 | } -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import './App.css'; 3 | import { Card ,Alert,Spin} from 'antd'; 4 | import { 5 | BrowserRouter as Router, 6 | Route, 7 | Link, 8 | Prompt, 9 | Redirect, 10 | Switch 11 | } from 'react-router-dom' 12 | import NumCardConX from './containers/NumCard' 13 | import SideMeu from './components/SideMeu' 14 | import Loadable from 'react-loadable' 15 | import Fu from './components/Fu'; 16 | import WiseCharts from './components/WiseCharts' 17 | import MoreWiseCharts from './components/MoreWiseCharts' 18 | import GridCharts from './components/GridCharts'; 19 | import SearchResults from './components/SearchResults'; 20 | 21 | const Login = (props) => { 22 | console.log(props) 23 | return 30 | 31 | } 32 | 33 | 34 | const Message = ({ 35 | match 36 | }) => { 37 | console.log(match) 38 | return (
39 |

ms

40 |

{ match.params.id}

41 |
) 42 | } 43 | const Inbox = ({ match }) => { 44 | return ( 45 | 46 |
47 | 48 |
49 |
50 | ) 51 | } 52 | 53 | const data={ 54 | week: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'], 55 | '邮件营销': [120, 132, 101, 134, 90, 230, 210], 56 | '联盟广告': [220, 182, 191, 234, 290, 330, 310], 57 | '视频广告': [150, 232, 201, 154, 190, 330, 410], 58 | '直接访问': [320, 332, 301, 334, 390, 330, 320], 59 | '搜索引擎': [820, 932, 901, 934, 1290, 1330, 1320], 60 | } 61 | 62 | const Loading=()=>{ 63 | return 64 | } 65 | 66 | 67 | const AddInputCon = Loadable({ 68 | loader: () => 69 | import ('./containers/AddInput'), 70 | loading: Loading 71 | }) 72 | const GetListCon = Loadable({ 73 | loader: () => 74 | import ('./containers/GetList'), 75 | loading: Loading 76 | }) 77 | const SagaAsyCon = Loadable({ 78 | loader: () => 79 | import ('./containers/SagaAsy'), 80 | loading: Loading 81 | }) 82 | 83 | 84 | 85 | 86 | 87 | class App extends Component { 88 | render() { 89 | return ( 90 | 91 |
92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | } /> 102 | } /> 103 | 104 | 105 | }/> 106 |
107 |
108 | ); 109 | } 110 | 111 | } 112 | 113 | export default App; -------------------------------------------------------------------------------- /src/actions/addInputAction.js: -------------------------------------------------------------------------------- 1 | const ADD_INPUT_ACTION = 'ADD_INPUT'; 2 | const STRING = 'STRING'; 3 | export const addInput = (text) => { 4 | return { 5 | type: ADD_INPUT_ACTION, 6 | text: text 7 | } 8 | } 9 | 10 | export const showText = (text) => { 11 | return { 12 | type: STRING, 13 | string: text 14 | } 15 | } -------------------------------------------------------------------------------- /src/actions/counterAction.js: -------------------------------------------------------------------------------- 1 | export const ADD_ACTION = 'ADD'; 2 | export const INCREMENT_ASYNC_ACTION = 'INCREMENT_ASYNC'; 3 | export const MINUS_ACTION = 'MINUS'; 4 | 5 | export const addOne = () => { 6 | return { 7 | type: ADD_ACTION 8 | } 9 | } 10 | 11 | export const addOneAsync = () => { 12 | return { 13 | type: INCREMENT_ASYNC_ACTION 14 | } 15 | } 16 | export const minusOne = () => { 17 | return { 18 | type: MINUS_ACTION 19 | } 20 | } -------------------------------------------------------------------------------- /src/actions/fetchAction.js: -------------------------------------------------------------------------------- 1 | const FETCH_REQUEST = 'FETCH_REQUEST'; 2 | const FETCH_FAILURE = 'FETCH_FAILURE'; 3 | const FETCH_SUCCESS = 'FETCH_SUCCESS'; 4 | 5 | export const fetchDataRequest = () => { 6 | return { 7 | type: FETCH_REQUEST, 8 | status: 'loading' 9 | } 10 | } 11 | export const fetchDataFailure = (error) => { 12 | return { 13 | type: FETCH_FAILURE, 14 | error 15 | } 16 | } 17 | export const fetchDataSuccess = (result) => { 18 | return { 19 | type: FETCH_SUCCESS, 20 | result 21 | } 22 | } 23 | 24 | //redux-thunk 版通过不同 name值 如good job share ask dev 25 | export const fetchData = (name) => { 26 | return (dispatch) => { 27 | const apiUrl = `https://cnodejs.org/api/v1/topics?tab=${name}`; 28 | dispatch(fetchDataRequest()) 29 | return fetch(apiUrl).then((response) => { 30 | if (response.status !== 200) { 31 | throw new Error('Fail to get response with status ' + response.status) 32 | } 33 | response.json().then((responseJson) => { 34 | console.log(responseJson) 35 | dispatch(fetchDataSuccess(responseJson.data)); 36 | }).catch((error) => { 37 | dispatch(fetchDataFailure(error)) 38 | }) 39 | }).catch((error) => { 40 | dispatch(fetchDataFailure(error)); 41 | }) 42 | 43 | } 44 | } -------------------------------------------------------------------------------- /src/actions/fetchAsyncAction.js: -------------------------------------------------------------------------------- 1 | const FETCH_ASYNC_REQUEST = 'FETCH_ASYNC_REQUEST'; 2 | const FETCH_ASYNC_FAILURE = 'FETCH_ASYNC_FAILURE'; 3 | const FETCH_ASYNC_SUCCESS = 'FETCH_ASYNC_SUCCESS'; 4 | 5 | export const fetchDataRequest = (name) => { 6 | return { 7 | type: FETCH_ASYNC_REQUEST, 8 | name 9 | } 10 | } 11 | export const fetchDataFailure = (error) => { 12 | return { 13 | type: FETCH_ASYNC_FAILURE, 14 | error 15 | } 16 | } 17 | export const fetchDataSuccess = (result) => { 18 | return { 19 | type: FETCH_ASYNC_SUCCESS, 20 | result 21 | } 22 | } -------------------------------------------------------------------------------- /src/components/Fu.js: -------------------------------------------------------------------------------- 1 | import React,{ Component} from 'react' 2 | import Zi from './Zi' 3 | export default class Fu extends Component{ 4 | constructor(props){ 5 | super(props) 6 | this.handleChange=this.handleChange.bind(this) 7 | this.state={number:0} 8 | } 9 | handleChange(){ 10 | console.log('父组件中的handleChange执行') 11 | this.setState((prevState)=>({ 12 | number:prevState.number+1 13 | })) 14 | } 15 | render(){ 16 | return ( 17 |
18 |
父组件{this.state.number}
19 | 20 |
21 | 22 | ) 23 | } 24 | 25 | } -------------------------------------------------------------------------------- /src/components/GridCharts.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import WiseCharts from './WiseCharts' 3 | import GridLayout from 'react-grid-layout'; 4 | import _ from "lodash"; 5 | import { Responsive, WidthProvider } from "react-grid-layout"; 6 | import '../../node_modules/react-grid-layout/css/styles.css' 7 | import '../../node_modules/react-resizable/css/styles.css' 8 | const ResponsiveReactGridLayout = WidthProvider(Responsive); 9 | const data={ 10 | week: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'], 11 | '邮件营销': [120, 132, 101, 134, 90, 230, 210], 12 | '联盟广告': [220, 182, 191, 234, 290, 330, 310], 13 | '视频广告': [150, 232, 201, 154, 190, 330, 410], 14 | '直接访问': [320, 332, 301, 334, 390, 330, 320], 15 | '搜索引擎': [820, 932, 901, 934, 1290, 1330, 1320], 16 | } 17 | function generateLayout() { 18 | return _.map(_.range(0, 4), function(item, i) { 19 | var y = Math.ceil(Math.random() * 10) + 1; 20 | return { 21 | x: (_.random(0, 5) * 3) % 12, 22 | y: Math.floor(i / 6) * y, 23 | w: 3, 24 | h: y, 25 | i: i.toString(), 26 | static: Math.random() < 0.05 27 | }; 28 | }); 29 | } 30 | 31 | class GridCharts extends Component{ 32 | static defaultProps = { 33 | className: "layout", 34 | rowHeight: 30, 35 | onLayoutChange: function() {}, 36 | cols: { lg: 12, md: 10, sm: 6, xs: 4, xxs: 2 }, 37 | initialLayout: generateLayout() 38 | }; 39 | 40 | state = { 41 | currentBreakpoint: "lg", 42 | compactType: "vertical", 43 | mounted: false, 44 | layouts: { lg: this.props.initialLayout } 45 | }; 46 | 47 | componentDidMount() { 48 | this.setState({ mounted: true }); 49 | } 50 | 51 | generateDOM() { 52 | return _.map(this.state.layouts.lg, function(l, i) { 53 | return ( 54 |
55 | {/* {l.static ? ( 56 | 60 | Static - {i} 61 | 62 | ) : ( 63 | {i} 64 | )} */} 65 | 66 |
67 | ); 68 | }); 69 | } 70 | 71 | onBreakpointChange = breakpoint => { 72 | this.setState({ 73 | currentBreakpoint: breakpoint 74 | }); 75 | }; 76 | 77 | onCompactTypeChange = () => { 78 | const { compactType: oldCompactType } = this.state; 79 | const compactType = 80 | oldCompactType === "horizontal" 81 | ? "vertical" 82 | : oldCompactType === "vertical" ? null : "horizontal"; 83 | this.setState({ compactType }); 84 | }; 85 | 86 | onLayoutChange = (layout, layouts) => { 87 | this.props.onLayoutChange(layout, layouts); 88 | }; 89 | 90 | onNewLayout = () => { 91 | this.setState({ 92 | layouts: { lg: generateLayout() } 93 | }); 94 | }; 95 | 96 | render() { 97 | return ( 98 |
99 | {/*
100 | Current Breakpoint: {this.state.currentBreakpoint} ({ 101 | this.props.cols[this.state.currentBreakpoint] 102 | }{" "} 103 | columns) 104 |
105 |
106 | Compaction type:{" "} 107 | {_.capitalize(this.state.compactType) || "No Compaction"} 108 |
*/} 109 | 110 | 113 | 126 | {this.generateDOM()} 127 | 128 |
129 | ); 130 | } 131 | } 132 | // render() { 133 | // // layout is an array of objects, see the demo for more complete usage 134 | // var layout = [ 135 | // {i: 'a', x: 0, y: 0, w: 8, h: 2, }, 136 | // {i: 'b', x: 0, y: 0, w: 3, h: 2, minW: 2, maxW: 4}, 137 | // {i: 'c', x: 3, y: 0, w: 5, h: 2}, 138 | // {i: 'd', x: 3, y: 0, w: 5, h: 2} 139 | // ]; 140 | // var style={background:'#ccc',border:'1px solid black'}; 141 | // return ( 142 | //
143 | // 144 | //
145 | // 146 | //
147 | //
148 | // 149 | //
150 | //
151 | // 152 | //
153 | //
154 | // 155 | //
156 | //
157 | //
158 | 159 | // ) 160 | // } 161 | // } 162 | export default GridCharts; 163 | 164 | -------------------------------------------------------------------------------- /src/components/MoreWiseCharts.js: -------------------------------------------------------------------------------- 1 | import React, { 2 | Component 3 | } from 'react'; 4 | 5 | import ReactEcharts from 'echarts-for-react'; 6 | import echarts from 'echarts' 7 | import './WiseCharts.css' 8 | 9 | const getKeys = data => Object.keys(data); 10 | 11 | export default class MoreWiseCharts extends Component { 12 | static defaultProps = { 13 | height: 400, 14 | title: '默认名称', 15 | type: 'line' 16 | }; 17 | 18 | constructor(props) { 19 | super(props); 20 | this.getOption = this.getOption.bind(this); 21 | } 22 | componentDidMount(){ 23 | const echartsInstanceOne=this.echarts_react_one.getEchartsInstance() 24 | const echartsInstancetwo=this.echarts_react_two.getEchartsInstance() 25 | echarts.connect([echartsInstanceOne,echartsInstancetwo]) 26 | } 27 | 28 | getOption() { 29 | setTimeout(() => { 30 | console.log(this.echarts_react) 31 | }, 1000); 32 | const [first, ...legendData] = getKeys(this.props.data); 33 | const legendDataArr = []; 34 | let legendTrStr = ''; 35 | legendData.forEach(item => { 36 | legendDataArr.push({ 37 | name: item, 38 | icon: 'circle' 39 | }); 40 | legendTrStr += `${item}`; 41 | 42 | }); 43 | 44 | return { 45 | title: { 46 | text: this.props.title, 47 | }, 48 | tooltip: { 49 | trigger: 'axis' 50 | }, 51 | legend: { 52 | bottom: '3%', 53 | type: 'scroll', 54 | data: legendDataArr 55 | }, 56 | 57 | dataset: { 58 | source: this.props.data 59 | }, 60 | grid: { 61 | left: '3%', 62 | right: '4%', 63 | bottom: '10%', 64 | containLabel: true 65 | }, 66 | 67 | toolbox: { 68 | show: true, 69 | feature: { 70 | dataZoom: { 71 | yAxisIndex: 'none' 72 | }, 73 | dataView: { 74 | readOnly: false, 75 | optionToContent: function otc(opt) { 76 | const timeArr = Object.values(opt.dataset[0].source); 77 | let table = ` 78 | ${legendTrStr} 79 | `; 80 | for (let i = 0, l = timeArr[0].length; i < l; i += 1) { 81 | let tdElm = ``; 82 | for (let j = 1; j < timeArr.length; j += 1) { 83 | const element = timeArr[j][i]; 84 | tdElm += ``; 85 | } 86 | table += `${tdElm} 87 | `; 88 | } 89 | table += '
时间
${element}
${timeArr[0][i]}
'; 90 | return table; 91 | }, 92 | }, 93 | magicType: { 94 | type: ['line', 'bar'] 95 | }, 96 | restore: {}, 97 | saveAsImage: {} 98 | } 99 | 100 | }, 101 | xAxis: { 102 | type: 'category', 103 | boundaryGap: false, 104 | }, 105 | yAxis: { 106 | type: 'value' 107 | }, 108 | series: [...legendData].fill({ 109 | type: this.props.type 110 | }), 111 | color: [ 112 | '#FF9C6E', '#FFC069', '#95DE64', '#5CDBD3', '#69C0FF', '#85A5FF', '#B37FEB', '#FF85C0' 113 | 114 | ] 115 | }; 116 | 117 | 118 | 119 | } 120 | 121 | 122 | 123 | render() { 124 | // const echarts_instance = this.echarts_react.getEchartsInstance(); 125 | // console.info(echarts_instance) 126 | // console.log(this.echarts_react) 127 | return ( 128 |
129 | { this.echarts_react_one = e; }} 138 | className = 'react_for_echarts' / > 139 | { this.echarts_react_two = e; }} 148 | className = 'react_for_echarts' / > 149 | 150 |
151 | 152 | )} 153 | } -------------------------------------------------------------------------------- /src/components/SearchResults.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import { Input } from 'antd'; 3 | import axios from 'axios'; 4 | 5 | const { Search } = Input; 6 | 7 | function SearchResults() { 8 | const [data, setData] = useState({ hits: [] }); 9 | const [query, setQuery] = useState('react'); 10 | 11 | useEffect(() => { 12 | let ignore = false; 13 | console.log(query) 14 | 15 | async function fetchData() { 16 | const result = await axios('https://hn.algolia.com/api/v1/search?query=' + query); 17 | if (!ignore) setData(result.data); 18 | } 19 | 20 | fetchData(); 21 | return () => { ignore = true; } 22 | }, [query]); 23 | 24 | const getData=(value)=>{ 25 | async function fetch() { 26 | const json=await axios ('https://hn.algolia.com/api/v1/search?query=' + value); 27 | setData(json.data); 28 | console.log(json.data); 29 | } 30 | fetch(); 31 | } 32 | 33 | return ( 34 |
35 | {/* setQuery(e.target.value)} /> */} 36 | {setQuery(value)}} 41 | /> 42 | getData(value)} 46 | />{query} 47 |
    48 | {data.hits.map(item => ( 49 |
  • 50 | {item.title} 51 |
  • 52 | ))} 53 |
54 |
55 | ); 56 | } 57 | 58 | export default SearchResults; 59 | 60 | 61 | -------------------------------------------------------------------------------- /src/components/SideMeu.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {Link} from 'react-router-dom' 3 | 4 | const SideMeu=()=>( 5 |
6 | 21 |
22 | ) 23 | 24 | export default SideMeu -------------------------------------------------------------------------------- /src/components/WiseCharts.1.js: -------------------------------------------------------------------------------- 1 | import React,{Component} from 'react'; 2 | import ReactEcharts from 'echarts-for-react'; 3 | export default class WiseCharts extends Component{ 4 | constructor(props){ 5 | super(props); 6 | this.getOption=this.getOption.bind(this); 7 | 8 | } 9 | getOption(){ 10 | const source={ 11 | week:['周一','周二','周三','周四','周五','周六','周日'], 12 | '邮件营销':[120, 132, 101, 134, 90, 230, 210], 13 | '联盟广告':[220, 182, 191, 234, 290, 330, 310], 14 | '视频广告':[150, 232, 201, 154, 190, 330, 410], 15 | '直接访问':[320, 332, 301, 334, 390, 330, 320], 16 | '搜索引擎':[820, 932, 901, 934, 1290, 1330, 1320], 17 | 18 | }; 19 | return { 20 | title: { 21 | text: '多折线图' 22 | }, 23 | tooltip: { 24 | trigger: 'axis' 25 | }, 26 | legend: { 27 | bottom: '3%', 28 | data:[ 29 | {name:'邮件营销',icon:'circle'}, 30 | {name:'联盟广告',icon:'circle'}, 31 | {name:'视频广告',icon:'circle'}, 32 | {name:'直接访问',icon:'circle'}, 33 | {name:'搜索引擎',icon:'circle'}, 34 | ] 35 | }, 36 | dataset:{ 37 | source 38 | 39 | }, 40 | grid: { 41 | left: '3%', 42 | right: '4%', 43 | bottom: '10%', 44 | containLabel: true 45 | }, 46 | toolbox: { 47 | show: true, 48 | feature: { 49 | dataZoom: { 50 | yAxisIndex: 'none' 51 | }, 52 | dataView: {readOnly: false}, 53 | magicType: {type: ['line', 'bar']}, 54 | restore: {}, 55 | saveAsImage: {} 56 | } 57 | }, 58 | xAxis: { 59 | type: 'category', 60 | boundaryGap: false, 61 | 62 | }, 63 | yAxis: { 64 | type: 'value' 65 | }, 66 | series: [ 67 | { 68 | type:'line' 69 | }, 70 | { 71 | type:'line' 72 | }, 73 | { 74 | type:'line' 75 | }, 76 | { 77 | type:'line' 78 | }, 79 | { 80 | type:'line' 81 | }, 82 | 83 | ], 84 | color: [ 85 | '#FF9C6E', '#FFC069', '#95DE64', '#5CDBD3', '#69C0FF', '#85A5FF', '#B37FEB', '#FF85C0' 86 | ] 87 | }; 88 | 89 | } 90 | render(){ 91 | return ( 92 | < ReactEcharts 93 | option = { 94 | this.getOption() 95 | } 96 | style = { 97 | { 98 | height: '350px', 99 | width: '60%' 100 | } 101 | } 102 | className = 'react_for_echarts' / > 103 | ) 104 | 105 | } 106 | } -------------------------------------------------------------------------------- /src/components/WiseCharts.css: -------------------------------------------------------------------------------- 1 | .reference { 2 | border-collapse: collapse; 3 | width: 100%; 4 | margin-bottom: 4px; 5 | margin-top: 4px; 6 | } 7 | 8 | table.reference tr { 9 | height: 40px; 10 | border-bottom: 1px solid #e8e8e8; 11 | } 12 | table.reference tr:nth-child(odd) { 13 | background-color: #f6f4f0; 14 | } 15 | table.reference tr:hover { 16 | background-color: #ff9c6e; 17 | } 18 | -------------------------------------------------------------------------------- /src/components/WiseCharts.js: -------------------------------------------------------------------------------- 1 | import React, { 2 | Component 3 | } from 'react'; 4 | 5 | import ReactEcharts from 'echarts-for-react'; 6 | import './WiseCharts.css' 7 | 8 | const getKeys = data => Object.keys(data); 9 | 10 | export default class WiseCharts extends Component { 11 | static defaultProps = { 12 | height: 400, 13 | width:'60%', 14 | title: '默认名称', 15 | type: 'line' 16 | }; 17 | 18 | constructor(props) { 19 | super(props); 20 | this.getOption = this.getOption.bind(this); 21 | } 22 | 23 | getOption() { 24 | const [first, ...legendData] = getKeys(this.props.data); 25 | const legendDataArr = []; 26 | let legendTrStr = ''; 27 | legendData.forEach(item => { 28 | legendDataArr.push({ 29 | name: item, 30 | icon: 'circle' 31 | }); 32 | legendTrStr += `${item}`; 33 | 34 | }); 35 | 36 | return { 37 | title: { 38 | text: this.props.title, 39 | }, 40 | tooltip: { 41 | trigger: 'axis' 42 | }, 43 | legend: { 44 | bottom: '3%', 45 | type: 'scroll', 46 | data: legendDataArr 47 | }, 48 | 49 | dataset: { 50 | source: this.props.data 51 | }, 52 | grid: { 53 | left: '3%', 54 | right: '4%', 55 | bottom: '10%', 56 | containLabel: true 57 | }, 58 | 59 | toolbox: { 60 | show: true, 61 | feature: { 62 | dataZoom: { 63 | yAxisIndex: 'none' 64 | }, 65 | dataView: { 66 | readOnly: false, 67 | optionToContent: function otc(opt) { 68 | const timeArr = Object.values(opt.dataset[0].source); 69 | let table = ` 70 | ${legendTrStr} 71 | `; 72 | for (let i = 0, l = timeArr[0].length; i < l; i += 1) { 73 | let tdElm = ``; 74 | for (let j = 1; j < timeArr.length; j += 1) { 75 | const element = timeArr[j][i]; 76 | tdElm += ``; 77 | } 78 | table += `${tdElm} 79 | `; 80 | } 81 | table += '
时间
${element}
${timeArr[0][i]}
'; 82 | return table; 83 | }, 84 | }, 85 | magicType: { 86 | type: ['line', 'bar'] 87 | }, 88 | restore: {}, 89 | saveAsImage: {} 90 | } 91 | 92 | }, 93 | xAxis: { 94 | type: 'category', 95 | boundaryGap: false, 96 | }, 97 | yAxis: { 98 | type: 'value' 99 | }, 100 | series: [...legendData].fill({ 101 | type: this.props.type 102 | }), 103 | color: [ 104 | '#FF9C6E', '#FFC069', '#95DE64', '#5CDBD3', '#69C0FF', '#85A5FF', '#B37FEB', '#FF85C0' 105 | 106 | ] 107 | }; 108 | 109 | 110 | 111 | } 112 | 113 | 114 | 115 | render() { 116 | let {width,height}=this.props; 117 | return ( 118 | 127 | )} 128 | } -------------------------------------------------------------------------------- /src/components/WiseCharts1.js: -------------------------------------------------------------------------------- 1 | import React,{Component} from 'react'; 2 | import ReactEcharts from 'echarts-for-react'; 3 | import {Icon} from 'antd'; 4 | export default class WiseCharts extends Component{ 5 | constructor(props){ 6 | super(props); 7 | this.getOption=this.getOption.bind(this); 8 | 9 | } 10 | getOption(){ 11 | return { 12 | title: { 13 | text: '折线图堆叠' 14 | }, 15 | tooltip: { 16 | trigger: 'axis' 17 | }, 18 | legend: { 19 | // data:['邮件营销','联盟广告','视频广告','直接访问','搜索引擎'] 20 | bottom:'3%', 21 | data:[ 22 | {name:'邮件营销',icon:'circle'}, 23 | {name:'联盟广告',icon:'circle'}, 24 | {name:'视频广告',icon:'circle'}, 25 | {name:'直接访问',icon:'circle'}, 26 | {name:'搜索引擎',icon:'circle'}, 27 | ] 28 | }, 29 | grid: { 30 | left: '3%', 31 | right: '4%', 32 | bottom: '10%', 33 | containLabel: true 34 | }, 35 | toolbox: { 36 | show: true, 37 | feature: { 38 | dataZoom: { 39 | yAxisIndex: 'none' 40 | }, 41 | dataView: {readOnly: false}, 42 | magicType: {type: ['line', 'bar']}, 43 | restore: {}, 44 | saveAsImage: {} 45 | } 46 | }, 47 | xAxis: { 48 | type: 'category', 49 | boundaryGap: false, 50 | data: ['周一','周二','周三','周四','周五','周六','周日'] 51 | }, 52 | yAxis: { 53 | type: 'value' 54 | }, 55 | series: [ 56 | { 57 | name:'邮件营销', 58 | type:'line', 59 | data:[120, 132, 101, 134, 90, 230, 210] 60 | }, 61 | { 62 | name:'联盟广告', 63 | type:'line', 64 | data:[220, 182, 191, 234, 290, 330, 310] 65 | }, 66 | { 67 | name:'视频广告', 68 | type:'line', 69 | data:[150, 232, 201, 154, 190, 330, 410] 70 | }, 71 | { 72 | name:'直接访问', 73 | type:'line', 74 | data:[320, 332, 301, 334, 390, 330, 320] 75 | }, 76 | { 77 | name:'搜索引擎', 78 | type:'line', 79 | data:[820, 932, 901, 934, 1290, 1330, 1320] 80 | } 81 | ], 82 | color: [ 83 | '#FF9C6E', '#FFC069', '#95DE64', '#5CDBD3', '#69C0FF', '#85A5FF', '#B37FEB', '#FF85C0' 84 | ] 85 | }; 86 | 87 | } 88 | render(){ 89 | return ( 90 | < ReactEcharts 91 | option = { 92 | this.getOption() 93 | } 94 | style = { 95 | { 96 | height: '350px', 97 | width: '60%' 98 | } 99 | } 100 | className = 'react_for_echarts' / > 101 | ) 102 | 103 | } 104 | } -------------------------------------------------------------------------------- /src/components/Zi.js: -------------------------------------------------------------------------------- 1 | import React,{ Component} from 'react' 2 | export default class Zi extends Component{ 3 | constructor(props) { 4 | super(props) 5 | this.clickChange = this.clickChange.bind(this) 6 | 7 | } 8 | clickChange() { 9 | this.props.changeData() 10 | } 11 | 12 | 13 | render(){ 14 | return ( 15 |
子组件点击{this.props.num}次
16 | ) 17 | } 18 | 19 | } -------------------------------------------------------------------------------- /src/containers/AddInput.js: -------------------------------------------------------------------------------- 1 | import React, { 2 | Component 3 | } from 'react' 4 | import { 5 | Card, 6 | Input 7 | } from 'antd' 8 | import { 9 | connect 10 | } from 'react-redux'; 11 | import { 12 | addInput, 13 | showText 14 | } from "../actions/addInputAction"; 15 | 16 | 17 | class AddInput extends Component { 18 | constructor(props) { 19 | super(props) 20 | this.handleClick = this.handleClick.bind(this) 21 | this.handleAddLeft = this.handleAddLeft.bind(this) 22 | } 23 | handleClick(value) { 24 | this.props.onAddInput(value) 25 | } 26 | handleAddLeft(value) { 27 | this.props.onShowText(value) 28 | } 29 | 30 | render() { 31 | const { 32 | showtext 33 | } = this.props; 34 | return ( 35 | 36 |
{ showtext}
37 |

38 | this.handleClick(value)}/> 41 | < Input.Search placeholder = "input search text" 42 | enterButton = "到左边" 43 | onSearch = {value => this.handleAddLeft(value)}/> 44 |

45 |
46 | 47 | ) 48 | } 49 | 50 | } 51 | 52 | function mapStateToProps(state) { 53 | return { 54 | showtext: state.addInput.showtext 55 | } 56 | 57 | } 58 | 59 | function mapDispatchToProps(dispatch) { 60 | return { 61 | onAddInput: (text) => dispatch(addInput(text)), 62 | onShowText: (str) => dispatch(showText(str)) 63 | } 64 | 65 | } 66 | 67 | const AddInputCon = connect( 68 | mapStateToProps, 69 | mapDispatchToProps 70 | )(AddInput) 71 | 72 | export default AddInputCon -------------------------------------------------------------------------------- /src/containers/DevTools.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | // Exported from redux-devtools 4 | import { createDevTools } from 'redux-devtools'; 5 | 6 | // Monitors are separate packages, and you can make a custom one 7 | import LogMonitor from 'redux-devtools-log-monitor'; 8 | import DockMonitor from 'redux-devtools-dock-monitor'; 9 | import ChartMonitor from 'redux-devtools-chart-monitor'; 10 | import SliderMonitor from 'redux-slider-monitor'; 11 | import Inspector from 'redux-devtools-inspector'; 12 | import FilterableLogMonitor from 'redux-devtools-filterable-log-monitor' 13 | 14 | 15 | 16 | 17 | 18 | 19 | // createDevTools takes a monitor and produces a DevTools component 20 | const DevTools = createDevTools( 21 | // Monitors are individually adjustable with props. 22 | // Consult their repositories to learn about those props. 23 | // Here, we put LogMonitor inside a DockMonitor. 24 | // Note: DockMonitor is visible by default. 25 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | ); 37 | 38 | export default DevTools; -------------------------------------------------------------------------------- /src/containers/GetList.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react' 2 | import {Card,Input,Spin,List} from 'antd' 3 | import { connect} from 'react-redux'; 4 | import {fetchData} from "../actions/fetchAction"; 5 | 6 | class GetList extends Component { 7 | constructor(props) { 8 | super(props) 9 | this.handleClick = this.handleClick.bind(this) 10 | } 11 | handleClick(value) { 12 | this.props.onSearchByName(value) 13 | } 14 | render(){ 15 | const {arrData,status } =this.props; 16 | // const items=arrData.map(item=> 17 | //
  • {item.title}
  • 18 | // ) 19 | return ( 20 | 21 |

    22 | this.handleClick(value)} 26 | /> 27 |

    28 |
    {status=='loading'?:""}
    29 | ({item.title})} 33 | /> 34 | 35 |
    36 | 37 | ) 38 | } 39 | 40 | } 41 | 42 | function mapStateToProps(state) { 43 | console.log(state.thunkReducer.data) 44 | return { 45 | arrData: state.thunkReducer.data, 46 | status: state.thunkReducer.status 47 | } 48 | 49 | } 50 | 51 | function mapDispatchToProps(dispatch) { 52 | return { 53 | onSearchByName: (name) => dispatch(fetchData(name)), 54 | } 55 | 56 | } 57 | 58 | const GetListCon = connect( 59 | mapStateToProps, 60 | mapDispatchToProps 61 | )(GetList) 62 | 63 | export default GetListCon -------------------------------------------------------------------------------- /src/containers/NumCard.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react' 2 | import {Card, Button} from 'antd' 3 | import { connect} from 'react-redux'; 4 | // import {addOne,addOneAsync} from "../actions/counterAction"; 5 | import * as CounterActions from '../actions/counterAction' 6 | 7 | 8 | class NumCard extends Component { 9 | constructor(props) { 10 | super(props) 11 | 12 | } 13 | componentDidMount() { 14 | const { 15 | dispatch 16 | } = this.props 17 | console.log("dispatch", dispatch) 18 | console.log("this.props", this.props) 19 | } 20 | render(){ 21 | const {count ,strText,addOne,minusOne,addOneAsync,onDecreaseClick} =this.props; 22 | console.log("this.props",this.props.dispatch) 23 | return ( 24 | 25 |
    {count}
    26 |
    {strText}
    27 |

    28 | 29 | 30 | 31 |

    32 |
    33 | 34 | ) 35 | } 36 | 37 | } 38 | 39 | function mapStateToProps(state) { 40 | console.log(state) 41 | return { 42 | count: state.counter.count, 43 | secCount: state.counter.secCount, 44 | strText: state.counter.strText 45 | } 46 | 47 | } 48 | // function mapDispatchToProps(dispatch) { 49 | // console.info(dispatch) 50 | // return { 51 | // onIncreaseClick:()=>dispatch(addOne()), 52 | // onDecreaseClick:()=>dispatch(minusOne()) 53 | // } 54 | 55 | // } 56 | 57 | // const NumCardCon= connect( 58 | // mapStateToProps, 59 | // {addOne,minusOne,addOneAsync} 60 | // )(NumCard) 61 | 62 | // export default NumCardCon 63 | // export default connect( 64 | // mapStateToProps, 65 | // {addOne,minusOne,addOneAsync} 66 | 67 | // )(NumCard) 68 | export default connect(mapStateToProps,{...CounterActions})(NumCard) -------------------------------------------------------------------------------- /src/containers/SagaAsy.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react' 2 | import {Card, Input,Spin,List} from 'antd' 3 | import { connect} from 'react-redux'; 4 | import {fetchDataRequest} from "../actions/fetchAsyncAction"; 5 | 6 | 7 | 8 | class SagaAsy extends Component { 9 | constructor(props) { 10 | super(props) 11 | this.handleClick = this.handleClick.bind(this) 12 | } 13 | handleClick(value) { 14 | this.props.onSearchByName(value) 15 | console.log(value) 16 | 17 | } 18 | 19 | render(){ 20 | const {arrData,status } =this.props; 21 | 22 | return ( 23 | 24 |

    25 | this.handleClick(value)} 29 | /> 30 |

    31 |
    {status=='loading'?:""}
    32 | ({item.title})} 36 | /> 37 | 38 |
    39 | ) 40 | } 41 | 42 | } 43 | 44 | function mapStateToProps(state) { 45 | console.log(state.sagaReducer) 46 | return { 47 | arrData: state.sagaReducer.data, 48 | status: state.sagaReducer.status 49 | } 50 | 51 | } 52 | 53 | function mapDispatchToProps(dispatch) { 54 | return { 55 | onSearchByName: (name) => dispatch(fetchDataRequest(name)), 56 | } 57 | 58 | } 59 | 60 | const SagaAsyCon = connect( 61 | mapStateToProps, 62 | mapDispatchToProps 63 | )(SagaAsy) 64 | 65 | export default SagaAsyCon -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: sans-serif; 5 | } 6 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom' 3 | import { Provider } from 'react-redux'; 4 | import './index.css'; 5 | import App from './App'; 6 | import store from './store' 7 | import registerServiceWorker from './registerServiceWorker'; 8 | import DevTools from './containers/DevTools'; 9 | 10 | 11 | ReactDOM.render( 12 | 13 |
    14 | {/* 方式Ⅰ 放开注释*/} 15 | 16 |
    17 |
    , 18 | document.getElementById('root')); 19 | registerServiceWorker(); -------------------------------------------------------------------------------- /src/reducers/addInput.js: -------------------------------------------------------------------------------- 1 | function addInput(state = { 2 | showtext: '沉良' 3 | }, action) { 4 | const showtext = state.showtext 5 | console.log(showtext) 6 | switch (action.type) { 7 | case 'ADD_INPUT': 8 | return { 9 | ...state, 10 | showtext: action.text 11 | } 12 | default: 13 | return state 14 | } 15 | 16 | } 17 | 18 | export default addInput; -------------------------------------------------------------------------------- /src/reducers/counter.js: -------------------------------------------------------------------------------- 1 | import { 2 | ADD_ACTION, 3 | ADD_ACTION_SUCCESS 4 | } from '../actions/counterAction'; 5 | 6 | function counter(state = { 7 | count: 0, 8 | secCount: 0 9 | }, action) { 10 | const count = state.count 11 | console.log(action) 12 | switch (action.type) { 13 | case 'ADD': 14 | return { ...state, 15 | count: count + 1, 16 | secCount: `${count+1}+` 17 | } 18 | case 'MINUS': 19 | return { ...state, 20 | count: count - 1, 21 | secCount: `${count-1}-` 22 | } 23 | case 'STRING': 24 | return { ...state, 25 | strText: action.string 26 | } 27 | default: 28 | return state 29 | } 30 | 31 | } 32 | 33 | export default counter; -------------------------------------------------------------------------------- /src/reducers/sagaReducer.js: -------------------------------------------------------------------------------- 1 | function sagaReducer(state = { 2 | data: [] 3 | }, action) { 4 | console.log(action) 5 | switch (action.type) { 6 | case 'FETCH_ASYNC_SUCCESS': 7 | return { ...state, 8 | data: action.result, 9 | status: 'success' 10 | } 11 | case 'FETCH_ASYNC_FAILURE': 12 | return { ...state, 13 | status: 'failure' 14 | } 15 | case 'FETCH_ASYNC_REQUEST': 16 | return { ...state, 17 | status: 'loading' 18 | } 19 | default: 20 | return state 21 | } 22 | 23 | } 24 | 25 | export default sagaReducer; -------------------------------------------------------------------------------- /src/reducers/thunkReducer.js: -------------------------------------------------------------------------------- 1 | function thunkReducer(state = { 2 | data: [] 3 | }, action) { 4 | console.log(action) 5 | switch (action.type) { 6 | case 'FETCH_SUCCESS': 7 | return { ...state, 8 | data: action.result, 9 | status: 'success' 10 | } 11 | case 'FETCH_FAILURE': 12 | return { ...state, 13 | status: 'failure' 14 | } 15 | case 'FETCH_REQUEST': 16 | return { ...state, 17 | status: 'loading' 18 | } 19 | default: 20 | return state 21 | } 22 | 23 | } 24 | 25 | export default thunkReducer; -------------------------------------------------------------------------------- /src/registerServiceWorker.js: -------------------------------------------------------------------------------- 1 | // In production, we register a service worker to serve assets from local cache. 2 | 3 | // This lets the app load faster on subsequent visits in production, and gives 4 | // it offline capabilities. However, it also means that developers (and users) 5 | // will only see deployed updates on the "N+1" visit to a page, since previously 6 | // cached resources are updated in the background. 7 | 8 | // To learn more about the benefits of this model, read https://goo.gl/KwvDNy. 9 | // This link also includes instructions on opting out of this behavior. 10 | 11 | const isLocalhost = Boolean( 12 | window.location.hostname === 'localhost' || 13 | // [::1] is the IPv6 localhost address. 14 | window.location.hostname === '[::1]' || 15 | // 127.0.0.1/8 is considered localhost for IPv4. 16 | window.location.hostname.match( 17 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 18 | ) 19 | ); 20 | 21 | export default function register() { 22 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 23 | // The URL constructor is available in all browsers that support SW. 24 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location); 25 | if (publicUrl.origin !== window.location.origin) { 26 | // Our service worker won't work if PUBLIC_URL is on a different origin 27 | // from what our page is served on. This might happen if a CDN is used to 28 | // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374 29 | return; 30 | } 31 | 32 | window.addEventListener('load', () => { 33 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 34 | 35 | if (isLocalhost) { 36 | // This is running on localhost. Lets check if a service worker still exists or not. 37 | checkValidServiceWorker(swUrl); 38 | 39 | // Add some additional logging to localhost, pointing developers to the 40 | // service worker/PWA documentation. 41 | navigator.serviceWorker.ready.then(() => { 42 | console.log( 43 | 'This web app is being served cache-first by a service ' + 44 | 'worker. To learn more, visit https://goo.gl/SC7cgQ' 45 | ); 46 | }); 47 | } else { 48 | // Is not local host. Just register service worker 49 | registerValidSW(swUrl); 50 | } 51 | }); 52 | } 53 | } 54 | 55 | function registerValidSW(swUrl) { 56 | navigator.serviceWorker 57 | .register(swUrl) 58 | .then(registration => { 59 | registration.onupdatefound = () => { 60 | const installingWorker = registration.installing; 61 | installingWorker.onstatechange = () => { 62 | if (installingWorker.state === 'installed') { 63 | if (navigator.serviceWorker.controller) { 64 | // At this point, the old content will have been purged and 65 | // the fresh content will have been added to the cache. 66 | // It's the perfect time to display a "New content is 67 | // available; please refresh." message in your web app. 68 | console.log('New content is available; please refresh.'); 69 | } else { 70 | // At this point, everything has been precached. 71 | // It's the perfect time to display a 72 | // "Content is cached for offline use." message. 73 | console.log('Content is cached for offline use.'); 74 | } 75 | } 76 | }; 77 | }; 78 | }) 79 | .catch(error => { 80 | console.error('Error during service worker registration:', error); 81 | }); 82 | } 83 | 84 | function checkValidServiceWorker(swUrl) { 85 | // Check if the service worker can be found. If it can't reload the page. 86 | fetch(swUrl) 87 | .then(response => { 88 | // Ensure service worker exists, and that we really are getting a JS file. 89 | if ( 90 | response.status === 404 || 91 | response.headers.get('content-type').indexOf('javascript') === -1 92 | ) { 93 | // No service worker found. Probably a different app. Reload the page. 94 | navigator.serviceWorker.ready.then(registration => { 95 | registration.unregister().then(() => { 96 | window.location.reload(); 97 | }); 98 | }); 99 | } else { 100 | // Service worker found. Proceed as normal. 101 | registerValidSW(swUrl); 102 | } 103 | }) 104 | .catch(() => { 105 | console.log( 106 | 'No internet connection found. App is running in offline mode.' 107 | ); 108 | }); 109 | } 110 | 111 | export function unregister() { 112 | if ('serviceWorker' in navigator) { 113 | navigator.serviceWorker.ready.then(registration => { 114 | registration.unregister(); 115 | }); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/sagas/sagas.js: -------------------------------------------------------------------------------- 1 | import {takeLatest,all,takeEvery,put,call,take,fork} from 'redux-saga/effects' 2 | import {fetchDataFailure,fetchDataRequest,fetchDataSuccess} from '../actions/fetchAsyncAction' 3 | import {ADD_ACTION, ADD_ACTION_SUCCESS,INCREMENT_ASYNC,INCREMENT_ASYNC_ACTION,addOneAsync,addOne} from '../actions/counterAction'; 4 | import {delay} from 'redux-saga' 5 | 6 | 7 | 8 | export default function* rootSaga () { 9 | // while (true) { 10 | // yield watchIncrementAsync() 11 | // yield watchGetDataByType() 12 | // } 13 | yield all([ 14 | watchIncrementAsync(), 15 | watchGetDataByType() 16 | ]) 17 | // 18 | } 19 | 20 | // export async function incrementAsync(){ 21 | // await delay(1000) 22 | // await put(addOneAsync()); 23 | // } 24 | export function* incrementAsync() { 25 | console.log('>>>>>incrementAsync') 26 | yield delay(1000) 27 | // yield put(addOneAsync()); 28 | yield put(addOne()); 29 | } 30 | 31 | export function* watchIncrementAsync() { 32 | yield takeEvery('INCREMENT_ASYNC', incrementAsync) 33 | } 34 | 35 | //用saga异步获取后台数据 36 | function fetchCnData(name) { 37 | return fetch(`https://cnodejs.org/api/v1/topics?tab=${name}`).then((response) => { 38 | if (response.status !== 200) { 39 | throw new Error('Fail to get response with status ' + response.status) 40 | } 41 | return response.json() 42 | }) 43 | 44 | } 45 | // 1.worker 46 | export function* getDataByType(name) { 47 | try { 48 | const response = yield call(fetchCnData, name); 49 | yield put(fetchDataSuccess(response.data)) 50 | } catch (error) { 51 | yield put(fetchDataFailure(error)) 52 | } 53 | } 54 | // 2.watcher 55 | export function* watchGetDataByType() { 56 | while (true) { 57 | const { 58 | name 59 | } = yield take('FETCH_ASYNC_REQUEST'); 60 | yield fork(getDataByType, name) 61 | } 62 | 63 | 64 | } -------------------------------------------------------------------------------- /src/store.js: -------------------------------------------------------------------------------- 1 | import { 2 | createStore, 3 | combineReducers, 4 | applyMiddleware, 5 | compose 6 | } from 'redux' 7 | import createSagaMiddleware from 'redux-saga' 8 | import counter from './reducers/counter' 9 | import addInput from './reducers/addInput' 10 | import thunkReducer from './reducers/thunkReducer' 11 | import sagaReducer from './reducers/sagaReducer' 12 | import logger from 'redux-logger' 13 | import thunk from 'redux-thunk' 14 | import rootSaga from './sagas/sagas' 15 | import DevTools from './containers/DevTools' 16 | import { composeWithDevTools } from 'redux-devtools-extension' 17 | 18 | 19 | 20 | const sagaMiddleware = createSagaMiddleware() 21 | 22 | const reducer = combineReducers({ 23 | counter, 24 | addInput, 25 | thunkReducer, 26 | sagaReducer 27 | }) 28 | // redux-devtools 方式Ⅰ 29 | // const enhancer = compose( 30 | // // Middleware you want to use in development: 31 | // applyMiddleware(sagaMiddleware, thunk, logger), 32 | // // Required! Enable Redux DevTools with the monitors you chose 33 | // DevTools.instrument() 34 | // ); 35 | // const store = createStore(reducer,enhancer ) 36 | 37 | // redux-devtools-extension 方式Ⅱ 38 | const store = createStore(reducer, composeWithDevTools( 39 | applyMiddleware(sagaMiddleware, thunk, logger), 40 | // other store enhancers if any 41 | )); 42 | 43 | sagaMiddleware.run(rootSaga) 44 | 45 | export default store --------------------------------------------------------------------------------