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
/* 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…