The React project

directory

A,Technology stack

Second,Project installation

Three,The project architecture

Four,test

Five,The deployment of

Vi.Entrance to the home page

Seven,Navigation menu

Eight,Redux-saga status sharing

Nine,Antd configuration

Ten,At the end

Eleven,Making a link


I. Technology stack

See package.json for details

  • The Create – react – 3.0 + app
  • Yarn
  • The React 16.9.0
  • Redux-saga
  • React Router
  • Less
  • Axios request library
  • Webpack 4.0 +
  • ES6 + Babel
  • Antd @ 3.23.6

Project Description:

1. Lazy route loading

2. The incorrect route matches 404

3.Api requests encapsulate Axios utility classes

4. Redux-saga handles asynchronous requests

Article 5. Nprogress loading

6. Route authentication

7. Decorator mode state sharing and form wrapping

8. Add the redux function

7. The interface is the Api of Java server side, which will not be extracted for the time being, so the project cannot be logged in normally. The PROJECT will provide UI visual draft, which can be borrowed for reference if there is corresponding demand. One last word, Redux-Saga smells good


Ii. Project installation

This project uses the YARN management dependency package, and yarn needs to be installed

Yarn install // Install depends on yarn start // RunCopy the code

Iii. Project architecture

⊙ Directory Structure

. ├ ─ config /# Webpack configuration directory├ ─ public /# template file├ ─ dist /# Build generated projects in the production environment├ ─ scripts /# Webpack environment variable configuration├ ─ SRC /# source directory (where all development takes place)│ ├ ─ assets /# Place static files that need to be processed by Webpack│ ├ ─ components /# components│ │ ├ ─ Layout /# Global layout│ │ ├ ─ PrivateRoute /# route guard│ ├ ─ store /# Redux-sagas│ │ ├ ─ the actions /# (Actions)│ │ ├ ─ reducers /# (Reducers)│ │ ├ ─ sagas /# (Sagas)│ │ ├ ─ index. Js# (Store file management)│ ├ ─ ─ the router /# ROUTE│ ├ ─ ─ service /SERVICE (unified Api management)│ ├ ─ ─ utils /# tool library│ ├ ─ ─ pages /# View pages│ ├ ─ ─ index. JsStart file│ ├ ─ ─ App. Js# Main entry page├ ─ ─ gitignoreGit ignore files (folders)├ ─ ─ package. JsonCopy the code

Four, test,

The test tool is not added


Five, deployment,

yarn build

After build, if you want to open in the local online environment, it is recommended to install an HTTP-server local server

npm install http-server -g

After the installation is successful, run the http-server command in the build folder to open the local server environment. You can also configure a port by yourself. For details, see http-server

The page is blank due to path problems after packaging

After the local server is opened in the build directory, the project may be blank and the resources cannot be loaded. You only need to configure the homepage property in package.json

//package.json file to add configuration
"homepage": ".".Copy the code

Six, home page entrance

The entry file mainly defines the routing page, because this project is a single page application, so the main entry only needs to configure three module routes, root route address/match
,login match < login />,404 match

/* webpackChunkName: “name” */ can be set to /* webpackChunkName: “name” */

// This file is the same bar that implements github page loading
import LoadableComponent from '@/utils/LoadableComponent'
const Login = LoadableComponent((a)= >import(/* webpackChunkName: "login" */ '@/pages/Login'))
Copy the code

The HashRouter routing mode is referenced here. BrowserRouter mode needs background cooperation, otherwise the current routing address will be blank error when packaging.

Entry file app.js all the code posted here

import React, { Component } from 'react'
import IndexLayout from '@/components/Layout/index'
import { connect } from 'react-redux';
import LoadableComponent from '@/utils/LoadableComponent'
import { HashRouter as Router, Switch, Route, Redirect } from 'react-router-dom'

const Login = LoadableComponent((a)= >import(/* webpackChunkName: "login" */ '@/pages/Login'))
const ErrorPage = LoadableComponent((a)= >import(/* webpackChunkName: "errorPage" */ '@/pages/ErrorPage'))

// The decorator mode links Redux data and saves a lot of complex code. Note: Unwanted objects can be passed null
@connect(
    state= > ({
        id_token: state.loginReducer.id_token
    })
)
class App extends Component {
    render() {
        return<Route exact path="/" render={() => <Redirect to="/apply" push />} /> <Route path="/404" Component ={ErrorPage} /> <Route path="/login" render={() => {return this.props. Id_token? <Redirect to="/" /> : <Login />}} /> // Master template component after Login <Route Render ={() => <IndexLayout />} /> </Switch> </Router>)}} export default AppCopy the code

Navigation menu

The layout of this project is left and right, with no Header and Footer. The interface is innovative…

import React, { Component } from 'react'
import ContentMain from '@/components/Layout/ContentMain'// Main content component import SliderNav from'@/components/Layout/SliderNav'// Menu bar component import {Layout} from'antd'

const { Content, Sider } = Layout;

class IndexLayout extends Component {

    render() {
        return (
            <Layout>
                <Sider
                    collapsible
                    trigger={null}
                >
                    <SliderNav/>
                </Sider>
                <Layout>
                    <Content style={{background: '#f7f7f7'}}>
                        <ContentMain/>
                    </Content>
                </Layout>
            </Layout>
        )
    }
}

export default IndexLayout
Copy the code

Menu bar code is not posted one by one, the content is a little long, the menu bar route column has entry configuration to leave sub-menu, as long as the corresponding route array is configured in accordance with the format. All navigation menus are laid out under the Layout folder in the Components library. This section provides the main route configuration information of the navigation bar. Note that the route configuration here is not the same information as the previous route configuration. The former is the total route address, and the route here is all the route information of the Content on the right.

import React, { Component } from 'react'
import { withRouter, Switch, Redirect, Route } from 'react-router-dom'
import LoadableComponent from '@/utils/LoadableComponent'
// Route authentication component, which wraps all 'Content' pages and jumps back to 'Login' page if token expires
import PrivateRoute from '@/components/PrivateRoute'

const Apply = LoadableComponent((a)= >import(/* webpackChunkName: "apply" */ '@/pages/Apply'))
const Case = LoadableComponent((a)= >import(/* webpackChunkName: "case" */ '@/pages/Case'))

@withRouter
class ContentMain extends Component {
    render () {
        return( <div style={{padding: '20px 32px'}}> <Switch> <PrivateRoute exact path='/apply' component={ Apply }/> <PrivateRoute exact path='/case' Component ={Case}/> //404 page matches route here <Route render={() => <Redirect to="/404" />} /> < exact from='/' to='/apply'/> </Switch> </div>) } } export default ContentMainCopy the code

Redux-saga status sharing

Before deciding to use Redux-Saga, we also considered using Redux-Thunk for state sharing because it was much easier to get started. But the redux-Thunk synchronous asynchronous code is all in the same file, and if there are too many single-page interfaces, the code will be spaghetti, which is not easy to understand and maintain. So I decided to quote Redux-Saga. There are a lot of pits in the middle, because you can hardly find the complete series of Redux-Saga on Github, and every time you get stuck, you have to browse through various materials. The following is a list of common problems encountered in the development process, so that you can read the project to avoid the pit.

1. Paste the store configuration file first

import { createStore, applyMiddleware, compose } from 'redux';
import createSagaMiddleware from 'redux-saga';
import reducer from './reducers';
import sagas from './sagas'
import { routerMiddleware } from 'react-router-redux';

const sagaMiddleware = createSagaMiddleware();
const createHistory = require('history').createHashHistory;
const history = createHistory();   // Initialize history
const routerWare = routerMiddleware(history);
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) : compose;
const enhancer = composeEnhancers(applyMiddleware(sagaMiddleware, routerWare));
const store = createStore(
	reducer,
	enhancer
)

sagaMiddleware.run(sagas)

export default store
Copy the code

The SAGAs introduced here are saga files sorted by page reference to avoid all asynchronous requests being written in the same file. Because this project only has three modules, login page, application page and case page, it is better to divide them into three modules.

// Saga modular introduction
import { fork, all } from 'redux-saga/effects'

// Asynchronous logic
import { loginSagas } from './login'
import { applySagas } from './apply'
import { caseSagas } from './case'

// Single entry point, start all Saga at once
export default function* rootSaga() {
    yield all([
        fork(loginSagas),
        fork(applySagas),
        fork(caseSagas)
    ])
}
Copy the code

I won’t elaborate on the proper use of redux-saga here, but if you want to use redux-saga you can refer to the official documentation. Or borrow the project source code, in accordance with the gourd ladle, write a few times more will naturally. However, you need to understand the Generator functions of Es6 first.

The specific asynchronous reference of Redux-Saga has been used in many places in the project. I will complete the tutorial of using Redux-Saga when I have enough time.

There are two potholes that are easy to encounter with redux-Saga

  • Indicates a route after the asynchronous request ends
  • Redux-saga asynchronous requests are only executed once, such as paging interfaces. How can you split pages that are only loaded the first time

① Redirect a route after the asynchronous request ends

React and VUE route redirects are a bit different. Vue encapsulates all routing information. Once you reference vue-Router, you can refer to any route you want. / /Link> or this.props. History. push(‘/login’). This.props.history. Push (‘/login’) does not work. To jump routes in the redux-saga state share, extra configuration is required.

1. Install history and react-router-redux first

yarn add history react-router-redux

2. Reference in store


import createSagaMiddleware from 'redux-saga';
import { routerMiddleware } from 'react-router-redux';

const sagaMiddleware = createSagaMiddleware();
const createHistory = require('history').createHashHistory;
const history= createHistory(); / / initializationhistory
const routerWare = routerMiddleware(history); const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) : compose; // routerWare is the middleware that packages the saga and state shared routing middleware. RouterWare is essential to redirect pages in saga. const enhancer = composeEnhancers(applyMiddleware(sagaMiddleware, routerWare));Copy the code

3. Apply in Saga

import { push } from 'react-router-redux';
function* login() {
  if(...). {// If the login succeeds, the route is redirected
    yield put(push('/login')) //Generator instruction jump}}Copy the code

② Redux-saga Asynchronous requests are executed only once

While (true){} should be added to each Generator method to yield all([]) listener. This allows you to re-listen each time a saga task request is completed (e.g. paging) for the same request, and then continue inside the Generator so that an interface is not requested only once.

This tiny Bug is really let me eat no small pain ah…

function * getSearchRequest() {
    while(true) {// Maintain the listening connection
        const resData = yield take(types.GET_SEARCH_DATA);
        const response = yield call(seachData, resData.payload)
        yield put(getSearchDataSuccess(response))
    }
}

function * getDetailRequest() {
    while(true) {const resData = yield take(types.GET_DRAFT_DETAIL_REQUEST);
        const response = yield call(searchDetail, resData.payload)
        yieldput(getDetailSuccess(response)); }}export function * caseSagas() {
    yield all([
        fork(getSearchRequest),
        fork(getDetailRequest)
    ]);
}

Copy the code

9. Antd configuration

This project refers to Antd UI component library, and loading all components at one time is of course too bloated. The on-demand loading configuration advice on the official website is before project YARN Run eject, which obviously does not meet the customization configuration of most online codes. Therefore, to configure Antd on-demand loading, you need to configure it separately

I’m going to put all the configuration of Babel here, just to solve two problems. Antd load and decorator mode configuration on demand.

Decorator mode installs dependencies and then configures Babel

yarn add babel-plugin-transform-decorators-legacy

//package.json
"babel": {
    "plugins": [["@babel/plugin-proposal-decorators".// Reference @connect, @withrouter decorator patterns must be configured with Babel
        {
          "legacy": true}], ["import".// ANTD loads the configuration on demand
        {
          "libraryName": "antd"."libraryDirectory": "es"."style": true // If set to true, the theme is custom, otherwise all ANTD CSS styles are loaded}}]]Copy the code

If antD loads the configuration on demand with the style attribute true, then the custom theme configuration is not finished.

In the webpack.config.js file,

// common function to get style loaders
const getStyleLoaders = (cssOptions, preProcessor) = > {
    const loaders = ...
    if (preProcessor) {
        let loader = {
            loader: require.resolve(preProcessor),
            options: {
                sourceMap: true,}}if (preProcessor === "less-loader") {
            // Below are all the theme colors of ANTD. More variables are available
            //https://github.com/ant-design/ant-design/blob/master/components/style/themes/default.less
            loader.options.modifyVars = {
                'primary-color': '#C89F64'.// Theme color
                'link-color': '#1DA57A'.// Theme link color
                'border-radius-base': '2px'
            }
            loader.options.javascriptEnabled = true
        }
        loaders.push(
            {
                loader: require.resolve('resolve-url-loader'),
                options: {
                    sourceMap: isEnvProduction && shouldUseSourceMap,
                },
            },
            loader
        );
    }
    return loaders;
};
Copy the code

Ten, the end

The whole project is actually developed, the only pity is that THE API request is not open, only for reference. Below I will attach a Github source code, including the design draft, holding the UI to look at the source code is not so laborious. It’s my pleasure~~~

React is a completely component-based development concept. Everything is a component. The project took 6 or 7 working days, not including the testing time, because time was too tight. – immutable.js will be added later, and react-hooks will be used to make it more powerful. Hey ha.

And finally, to be continued…


Github link

Github.com/zengxiaozen…