preface

Beep ———— Portal project address Github address

How could it work without a React combat project? The author recently happened to read about the react-hooks and Immutable data streams that God Sanyungang used in the gold mine. After studying the react-hooks and Immutable data streams project, I was inspired and used React to simply copy 58 Home APP.

React + hooks + redux + mocker-API + koa

Optimizes: better-scroll, Styled -component, react-config-router,react-lazyload, anti-shake, route lazy loading, Memo, etc

Without further ado, first the results:

The overall directory structure of the project is as follows:

├─ SRC │ ├─ API// Data request, interface│ ├ ─ assets// Static resources│ ├ ─ baseUI/ / UI components│ ├ ─ common// Common components│ ├ ─ components/ / component│ ├ ─ Data/ / data
│  ├─ index.css
│  ├─ index.js
│  ├─ layouts / / layout│ ├ ─ pages/ / page│ ├ ─ routes/ / routing│ ├─ ├─ trash// Local storage└ ─Copy the code

The front part

When developing a project application, we should first understand the overall structure of the project, so we will start with routing.

routing

We use react-router-config to configure routes.

configuration

  • Routes /index.js
import React from 'react';
import { Redirect, Link } from 'react-router-dom';
import BlankLayout from '.. /layouts/BlankLayout';
import Tabbuttom from '.. /components/tabbuttom/Tabbuttom';

import Main from '.. /pages/Main/Main';
import Detail from '.. /pages/details/Detail';

export default [{
    component: BlankLayout,
    routes: [{path: '/'.exact: true.render: () = > < Redirect to={"/home"}, {} / >,path: '/home'.component: Tabbuttom,
            routes: [{path: '/home'.exact: true.render: () = > < Redirect to={"/home/main"}, {} / >,path: '/home/main'.component: Main,
                }
            ],
        },
        {
            path: '/detail'.component: Detail,
            routes: [{path: "/detail/:id".component: Detail
                }
            ]
        }
    ]
}];
Copy the code
  • In order for the route to take effect, the route configuration must be imported into the App. The app.js code is as follows:

    The renderRoutes method will only render the first layer of the route. Now the App is the first layer. In order for this to work in the Main component, you just need to call renderRoutes again in the other child components such as Main.

import React from 'react';
import { BrowserRouter,HashRouter } from 'react-router-dom';
import {renderRoutes} from 'react-router-config';
import routes from './routes/index.js';

function App() {
  return (
    <div className="App">
      <HashRouter>
        {renderRoutes(routes)}
      </HashRouter>
    </div>
  );
}
export default App;
Copy the code

Route lazy loading

For a better user experience, we can use a combination of react. lazy and Suspense to optimize route lazy loading to improve the loading speed of the first screen and reduce the wait time for first-time users.

The idea is that we need a Suspense to wrap up when we use a component. While react. lazy takes a function as an argument, indicating that we are importing a component dynamically.

Suspense usage is not too verbose, but here we have a SuspenseComponent component wrapped around the component for use.

const Main = lazy(() = > import('.. /pages/Main/Main')); // How components are imported

const SuspenseComponent = Component= > props= > {
    return (
        <Suspense fallback={null}>
            <Component {. props} ></Component>
        </Suspense>)} {path: '/Main'.component: SuspenseComponent(Main)
}
Copy the code

redux

Redux is used here for data state management. As the application becomes more complex, the Reducer function needs to be split so that each page manages a portion of state independently. Finally, the **combineReducers ** auxiliary function was used to merge an object composed of multiple different reducer functions into a final Reducer function.

We then call createStore on this reducer to create a store, and whenever we dispatch an action on the store, the data in the store changes accordingly. We initialize store in the outermost container component, and then pass properties on state as props.

  • The store/reducer.js code is as follows:
import { combineReducers } from 'redux';
import { reducer as serverReducer } from ".. /pages/server/store/index";
import { reducer as orderReducer } from ".. /pages/details/store/index";
import { reducer as mainReducer } from '.. /pages/Main/store/index'
import { reducer as searchReducer } from '.. /pages/search/store/index'

// Merge the reducer
export default combineReducers({
    server: serverReducer,
    main: mainReducer,
    order: orderReducer,
    search: searchReducer
});
Copy the code
  • The store/index.js code is as follows:
import thunk from 'redux-thunk';
import { createStore, compose, applyMiddleware } from 'redux';
import reducer from "./reducer";
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
/ / create a store
const store = createStore(reducer, composeEnhancers(applyMiddleware(thunk)));

export default store;
Copy the code

The store in the sub-page takes the Main page as an example:

  • Main/store/constants.js

    // Define constants
    export const CHANGE_MAINDATA = 'CHANGE_MAINDATA';
    export const CHANGE_INDEX = 'CHANGE_INDEX';
    export const CHANGE_LISTITEMDATA = 'CHANGE_LISTITEMDATA';
    export const CHANGE_UPLOADING = 'CHANGE_UPLOADING';
    export const CHANGE_DOWNLOADING = 'CHANGE_DOWNLOADING';
    export const CHANGE_LIST_OFFSET = 'CHANGE_LIST_OFFSET';
    Copy the code
  • Main/store/reducer.js

    There is only one state corresponding to the update of return state and accept state of the reducer pure function.

    import * as actionTypes from './constants';
    // Initial state
    const defaultstate = {
        maindata: [].index: 0.ListItemData: [].listOffset: 0.Uploading: false.Downloading: false
    }
    const reducer = (state = defaultstate, action) = > {
        switch (action.type) {
            case actionTypes.CHANGE_MAINDATA:
                return{... state,maindata: action.data }
            case actionTypes.CHANGE_INDEX:
                return{... state,index: action.data }
            case actionTypes.CHANGE_LISTITEMDATA:
                return{... state,ListItemData: action.data }
            case actionTypes.CHANGE_UPLOADING:
                return{... state,Uploading: action.data }
            case actionTypes.CHANGE_DOWNLOADING:
                return{... state,Downloading: action.data } 
            case actionTypes.CHANGE_LIST_OFFSET:
                return{... state,listOffset: action.data }
    
            default:
                returnstate; }}export default reducer;
    Copy the code
  • The Main/store/actionCreators. Js code

    After the reqmain method successfully requests data, Diapatch (changeMainData) modifies the home page data and passes the successful data as a parameter to realize the modification of the home page data.

    import { reqmain, reqgetmainListoffset } from '.. /.. /.. /api/index';
    import * as actionType from './constants.js';
    
    // Modify home page data
    export const changeMainData = (data) = > {
        console.log("Successful entry...............");
        return {
            type: actionType.CHANGE_MAINDATA,
            data: data
        }
    }
    // Request home page data
    export const getMainData = () = > {
        return (dispatch) = > {
            reqmain().then((res) = > {
                if (res.data.success) {
                    dispatch(changeMainData(res.data.data))
                } else {
                    console.log("Failure", res);
                }
            }).catch((e) = > {
                console.log("Service page data request error!"); })}};Copy the code

At this point, the repository has been created, so how to use it?

Here we use the two objects provided by React-Redux, Provider and connect.

  • In the outermost container, wrap everything around the Provider component and pass the previously created Store as a prop to the Provider.
import React from 'react';
import { BrowserRouter,HashRouter } from 'react-router-dom';
import {Provider} from 'react-redux';

function App() {
  return (
    <Provider store={store}>
      	<div className="App">
            <Main></Main>
    	</div>
    </Provider>
  );
}
export default App;
Copy the code
  • Any component within the Provider (such as Main here) that requires data from the Store must be supplied byconnectPass component.
import React from 'react';

function Main() {
  return (
    <div></div>
  );
}
// This function allows us to bind the data in the store to the component as props
const mapStateToProps = (state) = > ({
    maindata: state.main.maindata

})
// This function binds action as props to Main.
const mapDispatchToProps = (dispatch) = > {
    return {
        getMainDataDispatch() {
            dispatch(actionTypes.getMainData())
        }
    }
}
export default connect(mapStateToProps, mapDispatchToProps)(memo(Main))
Copy the code

Scroll is super easy to use

Follow the big steps of Sanyuan and apply the Better Scroll in this project to create a silky sliding experience. The code and usage are directly listed here.

Click to learn three big scroll

In the project, we just need to wrap the component around Scroll. Notice here, the outside of the Scroll must have a layer that encloses the elements, and the inside of the Scroll must have the width and height set.

import React, { forwardRef, useState,useEffect, useRef, useImperativeHandle } from "react" import PropTypes from "prop-types" import BScroll from "better-scroll" import styled from'styled-components'; const ScrollContainer = styled.div` width: 100%; height: 100%; overflow: hidden; ` const Scroll = forwardRef ((props, ref) => { const [bScroll, setBScroll] = useState (); const scrollContaninerRef = useRef (); const { direction, click, refresh, bounceTop, bounceBottom } = props; const { pullUp, pullDown, onScroll } = props; useEffect (() => { const scroll = new BScroll (scrollContaninerRef.current, { scrollX: direction === "horizental", scrollY: direction === "vertical", probeType: 3, click: click, bounce:{ top: bounceTop, bottom: bounceBottom } }); setBScroll (scroll); return () => { setBScroll (null); } //eslint-disable-next-line }, []); useEffect (() => { if (! bScroll || ! onScroll) return; bScroll.on ('scroll', (scroll) => { onScroll (scroll); }) return () => { bScroll.off ('scroll'); } }, [onScroll, bScroll]); useEffect (() => { if (! bScroll || ! pullUp) return; Bscrollend ('scrollEnd', () => {if (bscrollend <= bscrolly + 100){pullUp (); }}); return () => { bScroll.off ('scrollEnd'); } }, [pullUp, bScroll]); useEffect (() => { if (! bScroll || ! pullDown) return; BScroll. On ('touchEnd', (pos) => {// Check the user's pullDown and make if (pos. Y > 50) {pullDown (); }}); return () => { bScroll.off ('touchEnd'); } }, [pullDown, bScroll]); useEffect (() => { if (refresh && bScroll){ bScroll.refresh (); }}); useImperativeHandle (ref, () => ({ refresh () { if (bScroll) { bScroll.refresh (); bScroll.scrollTo (0, 0); } }, getBScroll () { if (bScroll) { return bScroll; }}})); return ( <ScrollContainer ref={scrollContaninerRef}> {props.children} </ScrollContainer> ); }) Scroll.defaultProps = { direction: "vertical", click: true, refresh: true, onScroll:null, pullUpLoading: false, pullDownLoading: false, pullUp: null, pullDown: null, bounceTop: true, bounceBottom: true }; Scroll.propTypes = { direction: PropTypes.oneOf (['vertical', 'horizental']), refresh: PropTypes.bool, onScroll: PropTypes.func, pullUp: PropTypes.func, pullDown: PropTypes.func, pullUpLoading: PropTypes.bool, pullDownLoading: BounceBottom: proptypes. bool, bounceTop: proptypes. bool,// Whether the proptypes. bool is supported; export default Scroll;Copy the code
<div className="main">
     <Scroll direction={"vertical"} refresh={false} 
     </Scroll>
</div>
Copy the code

styled-components

Instead of using a traditional CSS file, we use gravitation-Components to create style components. Using Styled components, we can write styles in JSX files and do not have to worry about style naming, because each style.js is independent and the problem of defining too variable class name conflicts is solved. Styled components Step out of your comfort zone and try styled- Components.

Use the following

// jsx
return (
    <>
        <OrderTab>
         <div className='order-tab__icon'></div>
        </OrderTab>
    </>)
Copy the code
// style.js
import styled from "styled-components";
// Here we define OrderTab as a div tag
export const OrderTab = styled.div` font-family: PingFangSC-Regular; Height: 1.5648rem /* 169/108 */; background-color: #fff; & .order-tab__icon{ width: .7407rem /* 80/108 */; height: .7407rem /* 80/108 */; } `
Copy the code

Page rendering optimized Memo

If a component renders the same results with the same props, we can call it by wrapping it in react.Memo. Improve component performance by using Memo to render the results. In this case, React will skip rendering components and reuse the results of the last rendering directly to avoid unnecessary updates to child components.

function Main(props) {}export default React.memo(Main);
Copy the code

The backend part

On the front end, we wrap AXIos’ GET and POST requests in a function to facilitate subsequent data requests.

  • Create API/ajax. Js
import axios from 'axios';

export default function Ajax(url, data = {}, type) {
    return new Promise((resolve, rejet) = > {
        let Promise;
        if (type === 'GET') {
            Promise = axios.get(url, {
                params: data
            })
        } else {
            Promise = axios.post(url, {
                params: data
            })
        }
        Promise.then((response) = > {
            resolve(response);
        }).catch((error) = > {
            console.error("Abnormal data request!", error)
        })
    })
}
Copy the code
  • Create API /index.js, following is the actual data request.
import Ajax from './ajax.js';
export const reqserver = () = > {
    return Ajax("/home/server", {}, "GET");
}
export const reqmain = () = > {
    return Ajax("/home/main", {}, 'GET');
}
export const reqdetail = (data) = > {
    return Ajax("/detail", { data }, 'GET');
}
export const reqgetmainListoffset = (count) = > {
    return Ajax('/home/main', { count }, 'GET');
};

export const reqsearchkeywords = (keywords) = > {
    return Ajax("/search", { keywords }, 'GET');
};
export const reqsearchhot = () = > {
    return Ajax("/hot", {}, 'GET');
};
Copy the code

koa

Use KOA to set up back-end services

Shape interfaces using routes

We will create a new directory on the back end, with the following code:

const fs = require('fs')
const ServerData = require('./Data/serverData/ServerData.json')

const Koa = require('koa');// Introduce the KOA module
const router = require('koa-router') ();// Import routes
const app = new Koa();/ / instantiate
// Configure the route
router.get('/home/server'.async (ctx) => {
    ctx.response.body = {
        success:true.data:ServerData
    }
})

// Start the route
app
    .use(router.routes())
    .use(router.allowedMethods());
// The service starts locally on port 9090
app.listen(9090.() = > {
    console.log('server is running 9090');
});
Copy the code

Cross domain

Because the project is separated from the front and back ends, that is, the back end uses the local port 9090 to start the service, and the front end uses port 3000 to access the page, the front end requests the back end data must be cross-domain, and the browser reports an error. The KOA2-CORS plug-in is used here to solve cross-domain problems.

const cors = require('koa2-cors');

app.use(
    cors({
        origin: function(ctx) { // Set requests from the specified domain to be allowed
            // if (ctx.url === '/test') {
            return The '*'; // Allow requests from all domain names
            // }
            // return 'http://localhost:3000'; // only requests for http://localhost:8080 are allowed
        },
        maxAge: 5.// Specifies the validity period of this precheck request, in seconds.
        credentials: true.// Whether cookies can be sent
        allowMethods: ['GET'.'POST'.'PUT'.'DELETE'.'OPTIONS'].// Sets the allowed HTTP request methods
        allowHeaders: ['Content-Type'.'Authorization'.'Accept'].// Set all header fields supported by the server
        exposeHeaders: ['WWW-Authenticate'.'Server-Authorization'] // Set to get additional custom fields}))Copy the code

mockjs

You can also use MockJS to simulate data requests. Intercepts the outgoing request and returns local data.

import Mock from 'mockjs'; Export default Mock. Mock (/\/home\/server/, 'get', (options) => {console.log(" Mock in ", options); return { success: true, data: ServerData } });Copy the code

End scatter flower ~ interested friends welcome to github! If there are mistakes, please correct, thank you!