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

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"
                             }}>
                            &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;

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

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

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

 

  

原文地址:https://www.cnblogs.com/yangji0202/p/12349058.html

时间: 2024-10-07 11:43:04

Springboot + mybatis + React+redux+React-router+antd+Typescript(二): React+Typescrip项目的搭建的相关文章

IDEA+SpringBoot MyBatis Dynamic SQL的初体验(二)

在上节IDEA+SpringBoot MyBatis Dynamic SQL的初体验(一)中,讲解了Mybatis Dynamic SQL数据库生成https://www.cnblogs.com/hjm0928/p/9955228.html 现在那看一下怎么使用 先看一下项目结构 可以看到实体类,Mapper文件和DynamicSqlSupport文件都生成成功了 第一步 修改application.properties修改为application.yml  内容如下 spring: datas

react redux 深入剖析--干货

## 技术栈: react + redux + webpack + react-router + ES6/7/8 + immutable ## 运行项目(nodejs 6.0+) ``` git clone https://github.com/bailicangdu/react-pxq.git cd react-pxq npm i 或者运行 yarn(推荐) npm start npm run build (发布)``` ## 说明 > 本项目主要用于理解 react 和 redux 的编译方

IDEA+SpringBoot MyBatis Dynamic SQL的初体验(一)

ybatis generator在2017年12月发布了version 1.3.6,在该版本添加了新的TargetRuntime: MyBatis Dynamic SQL(mybatis 动态SQL)    Mybatis Dynamic Sql与以前TargetRuntime相比较: 移除了XXXExamle类和xml文件,代价是不兼容其他的TargetRuntime java版本限制,生成器将需要运行Java 8,低版本可能会出现错误 代码逻辑相对以前,使用了 lambda表达式,是的较容易

ReactJS React+Redux+Router+antDesign通用高效率开发模板,夜间模式为例

工作比较忙,一直没有时间总结下最近学习的一些东西,为了方便前端开发,我使用React+Redux+Router+antDesign总结了一个通用的模板,这个技术栈在前端开发者中是非常常见的. 总的来说,我这个工程十分便捷,对于初学者来说,可能包含到以下的一些知识点: 一.React-Router的使用 Router是为了方便管理组件的路径,它使用比较简单,一般定义如下就行,需要注意的是,react-router的版本有1.0-3.0,各个版本对应的API大致相似,但也有不同,我使用的是2.X的,

react+redux教程(四)undo、devtools、router

上节课,我们介绍了一些es6的新语法:react+redux教程(三)reduce().filter().map().some().every()....展开属性 今天我们通过解读redux-undo的官方示例代码来学习,在redux中使用撤销功能.devtools功能.以及router. 例子 这个例子是个计数器程序,包含计数器.右边的redux开发工具.还有一个路由(不过只有“/”这一个地址). 源代码: https://github.com/lewis617/myReact/tree/ma

React第六篇: 搭建React + Router + antd + nodejs + express框架搭建(nodejs做前后端server)

前提: nodejs >= 10.0;  这里不推荐用官网的yarn安装antd的模块,因为后续会出错,错误如图: 也不推荐用npx方法来搭建react骨架,也会出错,让我们开始吧!!   前端React+Antd框架搭建 1.安装并启动create-react-app骨架应用 打开cmd按顺序执行以下指令: npm install -g create-react-app   (全局安装create-react-app, 默认会安装在C盘个人用户下) create-react-app my-ap

webpack+react+redux+es6

一.预备知识 node, npm, react, redux, es6, webpack 二.学习资源 ECMAScript 6入门 React和Redux的连接react-redux Redux 入门教程   redux middleware 详解   Redux研究 React 入门实例教程 webpack学习demo NPM 使用介绍 三.工程搭建 之前有写过 webpack+react+es6开发模式 ,文章里介绍了一些简单的配置,欢迎访问. 1.可以npm init, 创建一个新的工程

webpack+react+redux+es6开发模式

一.预备知识 node, npm, react, redux, es6, webpack 二.学习资源 ECMAScript 6入门 React和Redux的连接react-redux Redux 入门教程   redux middleware 详解   Redux研究 React 入门实例教程 webpack学习demo NPM 使用介绍 三.工程搭建 之前有写过 webpack+react+es6开发模式 ,文章里介绍了一些简单的配置,欢迎访问. 1.可以npm init, 创建一个新的工程

React+Redux实现追书神器网页版

引言 由于现在做的react-native项目没有使用到redux等框架,写了一段时间想深入学习react,有个想法想做个demo练手下,那时候其实还没想好要做哪一个类型的,也看了些动漫的,小说阅读,聚合资源的开源项目.但是由于正好在学习开源的Android小说阅读器--任阅,加上api比较全,开始边学边做,项目地址在这里,如果有好的意见欢迎提issue或pr. 效果图 目录结构 ├─actions #redux的action,业务逻辑 ├─components #页面容器 │ └─common