• Springboot + mybatis + React+redux+React-router+antd+Typescript(二): React+Typescrip项目的搭建


    前言:

                后台搭建完以后开始搭建前端,使用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

        2.Typescript

        3.Redux

        4.React-Router

        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
    View Code
    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;
    View Code

        [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;
            };
        };
    }
    View Code

        

              [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"
                                 }}>
                                &nbsp;&nbsp;&nbsp;&nbsp;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;
    View Code

        [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组件会转圈圈,加载成功以后转圈圈消失. 

    --------------------

      本人照着以上流程走一遍,可以运行。

     

     

      

  • 相关阅读:
    Object-C中
    实例变量可见度修饰符
    Object-C 类和对象
    C语言中线程和进程的区别
    动态内存分配
    C语言中union关键字
    C语言结构体
    const define static extern 关键词详解
    基于TensorFlow Object Detection API进行迁移学习训练自己的人脸检测模型(一)
    Ubuntu18.04+CUDA9.0+cuDNN7.1.3+TensorFlow1.8 安装总结
  • 原文地址:https://www.cnblogs.com/yangji0202/p/12349058.html
Copyright © 2020-2023  润新知