前言:
后台搭建完以后开始搭建前端,使用create-react-app搭建项目非常方便。
前端主要是如何向后台请求数据,以及如何使用redux管理state,路由的配置.
前端github地址: https://github.com/www2388258980/rty-web
后台github地址: https://github.com/www2388258980/rty-service
项目访问地址: http://106.13.61.216:5000/ 账号/密码: root/root
准备工作:
1.需要安装node.js;
2.安装create-react-app脚手架;
准备知识:
1.React
3.Redux
5.adtd
以上点击都可以打开对应的文档.
正文:
1.使用 TypeScript 启动新的 Create React App 项目:
命令行:
>npx create-react-app 项目名 --typescript
or
>yarn create react-app 项目名 --typescript
然后运行:
>cd 项目名
>npm start
or
>yarn start
此时浏览器会访问 http://localhost:3000/ ,看到 Welcome to React
的界面就算成功了。
2. 按需引入antd组件库
[1]: 此时我们需要对 create-react-app 的默认配置进行自定义,这里我们使用 react-app-rewired (一个对 create-react-app 进行自定义配置的社区解决方案)。
>cd 项目名
>yarn add react-app-rewired
customize-cra
接着修改package.json
"scripts": {
"start": "react-app-rewired start",
"build": "react-app-rewired build",
"test": "react-app-rewired test",
}
[2]: 使用 babel-plugin-import,'是一个用于按需加载组件代码和样式的 babel 插件,现在我们尝试安装它并修改
config-overrides.js
文件。'
>yarn add babel-plugin-import
然后在项目根目录创建一个
config-overrides.js
用于修改默认配置.
const {override, fixBabelImports} = require('customize-cra'); module.exports = override( fixBabelImports('import', { libraryName: 'antd', libraryDirectory: 'es', style: 'css', }), );
[3]: 引入antd:
> yarn add antd
3. 封装ajax向后台请求数据:
[1]: > yarn add isomorphic-fetch
[2]: 在src下新建api目录,新建api.js和index.js
import fetch from 'isomorphic-fetch';//考虑使用fetch class _Api { constructor(opts) { this.opts = opts || {}; if (!this.opts.baseURI) throw new Error('baseURI option is required'); } request = (path, method = 'post', params, data, callback, urlType) => { return new Promise((resolve, reject) => { let url = this.opts.baseURI + path; if (urlType) { url = this.opts[urlType + 'BaseURI'] + path; } if (path.indexOf('http://') == 0) { url = path; } const opts = { method: method, }; if (this.opts.headers) { opts.headers = this.opts.headers; } if (data) { opts.headers['Content-Type'] = 'application/json; charset=utf-8'; opts.body = JSON.stringify(data); } if (params) { opts.headers['Content-Type'] = 'application/x-www-form-urlencoded'; let queryString = ''; for (const param in params) { const value = params[param]; if (value == null || value == undefined) { continue; } queryString += (param + '=' + value + '&'); } if (opts.method == 'get') { if (url.indexOf('?') != -1) { url += ('&' + queryString); } else { url += ('?' + queryString); } } else { opts.body = queryString; } } fetch(url, opts).then(function (response) { if (response.status >= 400) { throw new Error("Bad response from server"); } return response.json().then(function (json) { // callback(); return resolve(json); }) }).catch(function (error) { console.log(error); }); }) } } const Api = _Api; export default Api
import Api from './api'; const api = new Api({ baseURI: 'http://106.13.61.216:8888', // baseURI: 'http://127.0.0.1:8888', headers: { 'Accept': '*/*', 'Content-Type': 'application/json; charset=utf-8' } }); export default api;
4.接下来举个计时器的例子引入说明Redux,React-router.
[1]: 在react+typescript下引入Redux 管理状态:
> yarn add redux @types/redux
> yarn add react-redux @types/react-redux
[2]: 在src下新建containers目录,在containers目录下新建test目录,接着在此目录下新建action.tsx和index.tsx;
action.tsx:
const namespace = 'test'; // 增加 state 次数的方法 export function increment() { console.log("export function increment"); return { type: 'INCREMENT', isSpecial: true, namespace, } } // 减少 state 次数的方法 export const decrement = () => ({ type: 'DECREMENT', isSpecial: true, namespace })
index.tsx:
import * as React from 'react'; import {connect} from 'react-redux'; import {Dispatch, bindActionCreators} from 'redux'; import {decrement, increment} from '../test/action'; // 创建类型接口 export interface IProps { value: number; onIncrement: any; onDecrement: any; } // 使用接口代替 PropTypes 进行类型校验 class Counter extends React.PureComponent<IProps> { public render() { const {value, onIncrement, onDecrement} = this.props; console.log("value: " + value); console.log('onIncrement: ' + typeof onIncrement) return ( <p> Clicked: {value} times <br/> <br/> <button onClick={onIncrement} style={{marginRight: 20}}> +</button> <button onClick={onDecrement}> -</button> </p> ) } } // 将 reducer 中的状态插入到组件的 props 中 const mapStateToProps = (state: { counterReducer: any }) => ({ value: state.counterReducer.count }) // 将对应action 插入到组件的 props 中 const mapDispatchToProps = (dispatch: Dispatch) => ({ onDecrement: bindActionCreators(decrement, dispatch), onIncrement: bindActionCreators(increment, dispatch), }) // 使用 connect 高阶组件对 Counter 进行包裹 const CounterApp = connect( mapStateToProps, mapDispatchToProps )(Counter); export default CounterApp;
[3]: src下新建utils目录,新建promise.js,
export function isPromise(value) { if (value !== null && typeof value === 'object') { return value.promise && typeof value.promise.then === 'function'; } }
安装中间件:
>yarn add redux-thunk
在src下创建middlewares目录,新建promise-middleware.js(中间件),
import {isPromise} from '../utils/promise'; const defaultTypes = ['PENDING', 'FULFILLED', 'REJECTED']; export default function promiseMiddleware(config = {}) { const promiseTypeSuffixes = config.promiseTypeSuffixes || defaultTypes; return (_ref) => { const dispatch = _ref.dispatch; return next => action => { if(!isPromise(action.payload)){ let originType = action.originType?action.originType:action.type; let type = action.originType?action.type:action.namespace?`${action.namespace}_${action.type}`:`${action.type}`; return next({ ...action, originType, type }); } const {type, payload, meta,isSpecial, resultType,namespace} = action; const {promise, data} = payload; const [ PENDING, FULFILLED, REJECTED ] = (meta || {}).promiseTypeSuffixes || promiseTypeSuffixes; /** * Dispatch the first async handler. This tells the * reducers that an async action has been dispatched. */ next({ originType:type, type: namespace?`${namespace}_${type}_${PENDING}`:`${type}_${PENDING}`, ...!!data ? {payload: data} : {}, ...!!meta ? {meta} : {}, isSpecial, resultType, namespace }); const isAction = resolved => resolved && (resolved.meta || resolved.payload); const isThunk = resolved => typeof resolved === 'function'; const getResolveAction = isError => ({ originType:type, type: namespace?`${namespace}_${type}_${isError ? REJECTED : FULFILLED}`:`${type}_${isError ? REJECTED : FULFILLED}`, ...!!meta ? {meta} : {}, ...!!isError ? {error: true} : {}, isSpecial, resultType, namespace }); /** * Re-dispatch one of: * 1. a thunk, bound to a resolved/rejected object containing ?meta and type * 2. the resolved/rejected object, if it looks like an action, merged into action * 3. a resolve/rejected action with the resolve/rejected object as a payload */ action.payload.promise = promise.then( (resolved = {}) => { const resolveAction = getResolveAction(); return dispatch(isThunk(resolved) ? resolved.bind(null, resolveAction) : { ...resolveAction, ...isAction(resolved) ? resolved : { ...!!resolved && {payload: resolved} } }); }, (rejected = {}) => { const resolveAction = getResolveAction(true); return dispatch(isThunk(rejected) ? rejected.bind(null, resolveAction) : { ...resolveAction, ...isAction(rejected) ? rejected : { ...!!rejected && {payload: rejected} } }); }, ); return action; }; }; }
[4]:
(1)src下新建store目录,在此新建base-reducer.js,
const initialState = {}; export default function baseReducer(base, state = initialState, action = {}) { switch (action.type) { case `${base}_${action.originType}_PENDING`: return { ...state, [`${action.originType}Result`]: state[`${action.originType}Result`] ? state[`${action.originType}Result`] : null, [`${action.originType}Loading`]: true, [`${action.originType}Meta`]: action.meta, }; case `${base}_${action.originType}_SUCCESS`: return { ...state, [`${action.originType}Result`]: action.resultType ? action.payload[action.resultType] : action.payload, [`${action.originType}Loading`]: false, [`${action.originType}Meta`]: action.meta, }; case `${base}_${action.originType}_ERROR`: return { ...state, [`${action.originType}Error`]: action.payload.errorMsg, [`${action.originType}Loading`]: false }; case `${base}_${action.originType}`: return {...state, [action.originType]: action.data, [`${action.originType}Meta`]: action.meta}; default: return {...state}; } }
(2) 在store目录下新建reducers目录,接着新建test.tsx文件,
import baseReducer from '../base-reducer'; const namespace = 'test'; // 处理并返回 state export default function CounterReducer(state = {count: 0}, action: { type: string, isSpecial?: boolean }) { if (!action.isSpecial) { return baseReducer(namespace, state, action); } switch (action.type) { case namespace + '_INCREMENT': console.log('INCREMENT'); return Object.assign({}, state, { count: state.count + 1 //计数器加一 }); case namespace + '_DECREMENT': console.log('DECREMENT'); return Object.assign({}, state, { count: state.count - 1 //计数器减一 }); default: return state; } }
(3) 在store目录下新建configure.store.tsx文件,
import {createStore, applyMiddleware, combineReducers, compose} from 'redux'; import thunkMiddleware from 'redux-thunk'; import promiseMiddleware from '../middlewares/promise-middleware'; import CounterReducer from './reducers/test'; const reducer = combineReducers({ counterReducer: CounterReducer, }) const enhancer = compose( //你要使用的中间件,放在前面 applyMiddleware( thunkMiddleware, promiseMiddleware({promiseTypeSuffixes: ['PENDING', 'SUCCESS', 'ERROR']}) ), ); export default function configureStore(initialState = {}) { return createStore( reducer, initialState, enhancer ); }
[5]: 更改App.tsx中的内容:
import React from 'react'; // import './App.css'; import {Link} from 'react-router-dom'; import {Layout, Menu, Icon} from 'antd'; const {SubMenu} = Menu; const {Header, Content, Sider} = Layout; export interface AppProps { } export interface AppState { } class App extends React.Component<AppProps, AppState> { rootSubmenuKeys = ['拨入', '审计', '测试']; state = { collapsed: false, openKeys: ['拨入'], }; componentDidMount(): void { } toggle = () => { this.setState({ collapsed: !this.state.collapsed, }); }; onOpenChange = (openKeys: Array<string>) => { let latestOpenKey = openKeys.find(key => this.state.openKeys.indexOf(key) === -1); latestOpenKey = latestOpenKey ? latestOpenKey : ''; if (this.rootSubmenuKeys.indexOf(latestOpenKey) === -1) { this.setState({openKeys}); } else { this.setState({ openKeys: latestOpenKey ? [latestOpenKey] : [], }); } }; render() { const mainSvg = () => ( <svg className="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3350" width="30" height="30"> <path d="M512 170.666667c-11.946667 0-22.186667-4.266667-30.72-12.8-7.970133-7.970133-11.946667-17.92-11.946667-29.866667s3.976533-22.186667 11.946667-30.72c8.533333-7.970133 18.773333-11.946667 30.72-11.946667s21.896533 3.976533 29.866667 11.946667c8.533333 8.533333 12.8 18.773333 12.8 30.72s-4.266667 21.896533-12.8 29.866667c-7.970133 8.533333-17.92 12.8-29.866667 12.8z" p-id="3351" fill="#1296db"></path> <path d="M768 955.733333a17.015467 17.015467 0 0 1-12.066133-5.000533L725.333333 920.132267l-30.600533 30.600533a17.0496 17.0496 0 0 1-24.132267 0L640 920.132267l-30.600533 30.600533a17.0496 17.0496 0 0 1-24.132267 0L554.666667 920.132267l-30.600534 30.600533a17.0496 17.0496 0 0 1-24.132266 0L469.333333 920.132267l-30.600533 30.600533a17.0496 17.0496 0 0 1-24.132267 0L384 920.132267l-30.600533 30.600533a17.0496 17.0496 0 0 1-24.132267 0L298.666667 920.132267l-30.600534 30.600533a17.0496 17.0496 0 1 1-24.132266-24.132267l42.666666-42.666666a17.0496 17.0496 0 0 1 24.132267 0L341.333333 914.5344l30.600534-30.600533a17.0496 17.0496 0 0 1 24.132266 0L426.666667 914.5344l30.600533-30.600533a17.0496 17.0496 0 0 1 24.132267 0L512 914.5344l30.600533-30.600533a17.0496 17.0496 0 0 1 24.132267 0L597.333333 914.5344l30.600534-30.600533a17.0496 17.0496 0 0 1 24.132266 0L682.666667 914.5344l30.600533-30.600533a17.0496 17.0496 0 0 1 24.132267 0l42.666666 42.666666A17.0496 17.0496 0 0 1 768 955.733333z m-469.333333-128a17.015467 17.015467 0 0 1-12.066134-5.000533l-42.666666-42.666667a17.0496 17.0496 0 1 1 24.132266-24.132266l30.737067 30.754133 20.5824-20.104533 26.043733-88.712534v-0.0512l99.805867-341.230933 0.017067-0.1024 45.038933-152.6272a60.040533 60.040533 0 0 1-21.0944-13.9264c-11.229867-11.229867-16.930133-25.344-16.930133-41.9328 0-16.366933 5.563733-30.6176 16.554666-42.359467C481.3824 73.8304 495.633067 68.266667 512 68.266667c16.5888 0 30.702933 5.700267 41.9328 16.964266A58.504533 58.504533 0 0 1 571.733333 128c0 16.571733-6.2976 31.214933-18.210133 42.3424a53.589333 53.589333 0 0 1-19.848533 13.448533l44.936533 152.337067 0.238933 0.733867 50.1248 169.9328 0.238934 0.750933 50.1248 169.9328 0.221866 0.750933 25.975467 88.490667 19.831467 19.831467 30.600533-30.600534a17.0496 17.0496 0 1 1 24.132267 24.132267l-42.666667 42.666667a17.0496 17.0496 0 0 1-24.132267 0L682.666667 792.132267l-30.600534 30.600533a17.0496 17.0496 0 0 1-24.132266 0L597.333333 792.132267l-30.600533 30.600533a17.0496 17.0496 0 0 1-24.132267 0L512 792.132267l-30.600533 30.600533a17.0496 17.0496 0 0 1-24.132267 0L426.666667 792.132267l-30.600534 30.600533a17.0496 17.0496 0 0 1-24.132266 0l-30.2592-30.242133-31.0784 30.395733a17.1008 17.1008 0 0 1-11.9296 4.846933zM597.333333 750.933333c4.369067 0 8.738133 1.672533 12.066134 5.000534l30.600533 30.600533 27.630933-27.630933L650.257067 699.733333H374.596267l-17.5616 59.835734 26.9824 26.965333 30.600533-30.600533a17.0496 17.0496 0 0 1 24.132267 0l30.600533 30.600533 30.600533-30.600533a17.0496 17.0496 0 0 1 24.132267 0l30.600533 30.600533 30.600534-30.600533A16.9472 16.9472 0 0 1 597.333333 750.933333z m-212.6848-85.333333h255.556267l-40.260267-136.533333H424.9088l-40.260267 136.533333z m50.2272-170.666667h154.999467l-40.277333-136.533333h-75.1104l-39.611734 136.533333z m49.595734-170.666666h55.04L512 230.980267 484.471467 324.266667zM512 102.4c-7.645867 0-13.704533 2.338133-19.063467 7.355733-4.1984 4.539733-6.536533 10.5984-6.536533 18.244267 0 7.406933 2.2016 13.056 6.946133 17.783467 5.256533 5.256533 11.093333 7.748267 18.346667 7.816533h0.631467c7.099733-0.068267 12.373333-2.3552 17.066666-7.389867 5.922133-5.5808 8.209067-10.939733 8.209067-18.210133 0-7.406933-2.491733-13.329067-7.799467-18.653867C525.073067 104.6016 519.406933 102.4 512 102.4z" p-id="3352" fill="#1296db"></path> </svg> ); const boruSvg = () => ( <svg className="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7168" width="20" height="20"> <path d="M714 762.2h-98.2c-16.6 0-30 13.4-30 30s13.4 30 30 30H714c16.6 0 30-13.4 30-30s-13.4-30-30-30zM487.4 762.2H147.1c-16.6 0-30 13.4-30 30s13.4 30 30 30h340.3c16.6 0 30-13.4 30-30s-13.4-30-30-30z" fill="#a6adb4" p-id="7169"></path> <path d="M838.253 130.023l65.548 65.548-57.982 57.983-65.549-65.549z" fill="#a6adb4" p-id="7170"></path> <path d="M743.7 955.9H195.8c-53.7 0-97.4-43.7-97.4-97.4V174.8c0-53.7 43.7-97.4 97.4-97.4H615c16.6 0 30 13.4 30 30s-13.4 30-30 30H195.8c-20.6 0-37.4 16.8-37.4 37.4v683.7c0 20.6 16.8 37.4 37.4 37.4h547.9c20.6 0 37.4-16.8 37.4-37.4v-395c0-16.6 13.4-30 30-30s30 13.4 30 30v395.1c0 53.6-43.7 97.3-97.4 97.3z" fill="#a6adb4" p-id="7171"></path> <path d="M907.7 122.1l-39.2-39.2c-24-24-65.1-21.9-91.7 4.7L419.5 445 347 643.6l198.6-72.4L903 213.8c12.1-12.1 19.6-27.7 21.1-44 1.8-18.1-4.3-35.5-16.4-47.7zM512.6 519.3L447.5 543l23.7-65.1 264.7-264.7 40.9 41.7-264.2 264.4z m348-347.9l-41.3 41.3-40.9-41.7 40.9-40.9c3.1-3.1 6.2-3.9 7.6-3.9l37.6 37.6c-0.1 1.3-0.9 4.5-3.9 7.6z" fill="#a6adb4" p-id="7172"></path> </svg> ); const testSVg = () => ( <svg className="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4879" width="20" height="20"> <path d="M199.111111 1024c-62.577778 0-113.777778-51.2-113.777778-113.777778V227.555556c0-62.577778 51.2-113.777778 113.777778-113.777778 19.911111 0 36.977778 17.066667 36.977778 36.977778v54.044444c-42.666667 11.377778-73.955556 25.6-73.955556 71.111111V853.333333c0 51.2 39.822222 93.866667 93.866667 93.866667h497.777778c51.2 0 93.866667-42.666667 93.866666-93.866667V275.911111c0-45.511111-31.288889-59.733333-76.8-68.266667V153.6c-2.844444-22.755556 14.222222-39.822222 34.133334-39.822222 62.577778 0 113.777778 51.2 113.777778 113.777778v682.666666c0 62.577778-51.2 113.777778-113.777778 113.777778H199.111111z m341.333333-304.355556h227.555556c19.911111 0 36.977778 17.066667 36.977778 36.977778S787.911111 796.444444 768 796.444444h-227.555556c-19.911111 0-36.977778-17.066667-36.977777-36.977777s17.066667-39.822222 36.977777-39.822223z m0-301.511111h227.555556c19.911111 0 36.977778 17.066667 36.977778 36.977778s-17.066667 36.977778-36.977778 36.977778h-227.555556c-19.911111 0-36.977778-17.066667-36.977777-36.977778s17.066667-36.977778 36.977777-36.977778z m-227.555555-227.555555V150.755556c0-19.911111 17.066667-36.977778 36.977778-36.977778h36.977777c0-62.577778 51.2-113.777778 113.777778-113.777778s113.777778 51.2 113.777778 113.777778H654.222222c19.911111 0 36.977778 17.066667 36.977778 36.977778v36.977777L312.888889 190.577778z m-99.555556 233.244444c5.688889-5.688889 17.066667-5.688889 25.6 0l62.577778 62.577778 136.533333-136.533333c8.533333-8.533333 22.755556-8.533333 28.444445 0 8.533333 8.533333 8.533333 19.911111 0 28.444444l-147.911111 147.911111c-5.688889 2.844444-11.377778 5.688889-17.066667 5.688889-5.688889 0-8.533333-2.844444-11.377778-5.688889l-73.955555-73.955555c-11.377778-11.377778-11.377778-22.755556-2.844445-28.444445z m207.644445 403.911111c-8.533333 8.533333-22.755556 8.533333-28.444445 0L332.8 768l-59.733333 59.733333c-8.533333 8.533333-22.755556 8.533333-28.444445 0-8.533333-8.533333-8.533333-22.755556 0-28.444444l59.733334-59.733333-59.733334-59.733334c-8.533333-8.533333-8.533333-22.755556 0-28.444444 8.533333-8.533333 19.911111-8.533333 28.444445 0l59.733333 59.733333 59.733333-59.733333c8.533333-8.533333 22.755556-8.533333 28.444445 0 8.533333 8.533333 8.533333 19.911111 0 28.444444L361.244444 739.555556l59.733334 59.733333c8.533333 5.688889 8.533333 19.911111 0 28.444444z" fill="#9ca4ac" p-id="4880"></path> </svg> ) const MainIcon = (props: any) => <Icon component={mainSvg} {...props} />; const BoruIcon = (props: any) => <Icon component={boruSvg} {...props} />; const TestIcon = (props: any) => <Icon component={testSVg} {...props} />; return ( <div className="app" > <Layout> <Sider className="slider" collapsible collapsed={this.state.collapsed} trigger={null} width={250}> <div className="logo" style={{ height: 32, background: 'rgba(255, 255, 255, 0.2)', margin: 16, fontSize: 20, color: "orange" }}> rty <MainIcon style={{color: 'red'}}/> </div> <Menu theme="dark" mode="inline" defaultSelectedKeys={['1']} defaultOpenKeys={['审计']} style={{borderRight: 0,height: 950}} openKeys={this.state.openKeys} onOpenChange={this.onOpenChange} > <SubMenu key="拨入" title={<span><BoruIcon/>拨入</span>} > <Menu.Item key="1"><Link to="/boru/insert-record">新增拨入记录</Link></Menu.Item> <Menu.Item key="2"><Link to="/boru/query-record">查询拨入记录</Link></Menu.Item> <Menu.Item key="3"><Link to="/boru/insert-person">拨入人员列表</Link></Menu.Item> <Menu.Item key="4"><Link to="/boru/query-person">查询拨入人员列表</Link></Menu.Item> <Menu.Item key="5"><Link to="/boru/query-person-his">账号变更查询</Link></Menu.Item> </SubMenu> <SubMenu key="审计" title={<span><Icon type="user"/>审计</span>} > <Menu.Item key="6"><Link to="/shenji/insert-oa">添加OA账号</Link></Menu.Item> <Menu.Item key="7"><Link to="/shenji/query-oa">账号查询</Link></Menu.Item> <Menu.Item key="8"><Link to="/shenji/query-oa-his">账号变更查询</Link></Menu.Item> </SubMenu> <SubMenu key="测试" title={<span><TestIcon/>测试</span>} > <Menu.Item key="10"><Link to="/test">计时器测试</Link></Menu.Item> </SubMenu> </Menu> </Sider> <Layout> <Header style={{background: '#fff', padding: 0}}> <Icon className="trigger" type={this.state.collapsed ? 'menu-unfold' : 'menu-fold'} onClick={this.toggle} /> </Header> <Content style={{ margin: '6px 4px', padding: 6, background: '#ffffff', minHeight: 280, }} > {this.props.children} </Content> </Layout> </Layout> </div> ); } } export default App;
[6]: > yarn add react-router-dom @types/react-router-dom
然后在src下新建MyRouer.tsx,
/* 定义组件路由 * @author: yj * 时间: 2019-12-18 */ import React from 'react'; import {BrowserRouter as Router, Route, Switch} from 'react-router-dom'; import CounterApp from './containers/test/index';
import App from "./App"; const MyRouter = () => ( <Router> <Route path="/" render={() => <App> <Switch> <Route path="/test" component={CounterApp}/> <Route path="/" component={CounterApp}/> </Switch> </App> }/> </Router> ); export default MyRouter;
[7]: 更改src/index.tsx中的内容,
import React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; import * as serviceWorker from './serviceWorker'; import {Provider} from 'react-redux'; import configureStore from './store/configure-store'; import MyRouterApp from './MyRouter'; const store = configureStore(); ReactDOM.render( //关联store <Provider store={store}> <MyRouterApp/> </Provider>, document.getElementById('root')); // If you want your app to work offline and load faster, you can change // unregister() to register() below. Note this comes with some pitfalls. // Learn more about service workers: https://bit.ly/CRA-PWA serviceWorker.unregister();
-------------------------------
然后启动项目即可.总结一下工作流程以及容易误解的地方:
(1): 点击 '+'号,触发onIncrement函数,onIncrement通过'bindActionCreators'绑定了action的'increment'方法,本次action描述'计时器'要加1,派发action会交给reduce处理,reducer会改变state,
即state.counterReducer.count值加1,然后计时器重新渲染,值变成1.
(2): 本次的中间件会让api请求后台数据变成异步;
(3): 当action.isSpecial为false的时候,base-reducer.js是用来统一处理action的reducer,
import api from '../../api/index';
import {rtyDialOAPersonReq, rtyDialOAPerson} from './data'
const namespace = 'shenji';
export function getRtyOADialPersonsByFirstChar(firstChar?: string) {
let path = '/rtyOADialPersons/getRtyOADialPersonsByFirstChar';
return {
type: 'rtyOADialPersonsByFirstCharSource',
payload: {
promise: api.request(path, 'post', {firstChar})
},
resultType: 'data',
namespace
}
}
例如这个action,它会向 /rtyOADialPersons/getRtyOADialPersonsByFirstChar 接口请求数据, 拿到的json数据为:
{"JSON":{"status":"success","errorCode":null,"message":"查询成功.","data":[{"dialPersonId":"6","firstName":"美羊羊","telecomNumber":"12341231234","description":"羊村之美羊羊-臭美","firstChar":"meiyangyang","departmentId":"1004","status":"是","createdBy":"root","modifiedBy":"root","billId":"DSFsdf34543fds","modifiedBillId":"7777777777","opType":"更新","effectiveDate":"2020-02-11 13:17:10","lastUpdatedStamp":"2020-02-21 10:05:23","createdStamp":"2020-02-11 13:17:28"}],"total":0,"success":true},
'resultType'表示从后台返回的json对象拿属性为data的数据,
[{"dialPersonId":"6","firstName":"美羊羊","telecomNumber":"12341231234",......,"createdStamp":"2020-02-11 13:17:28"}
'type'表示经过base-reducer.js处理后,把上面的数据封装到 rtyOADialPersonsByFirstCharSourceResult 里面.rtyOADialPersonsByFirstCharSourceLoding一般和antd组件的loading属性绑定,
当数据加载的时候antd组件会转圈圈,加载成功以后转圈圈消失.
--------------------
本人照着以上流程走一遍,可以运行。