preface

After learning React for a period of time, I plan to write a simulated mobile terminal project to train my actual project ability and prepare for spring recruitment. Next, I will introduce the main project and difficulties I encountered. The online address and source code of the project are at the end of the article.

The finished product to show

This is what the project’s home pages look like

The project structure

| β”œ ─ Data / / Data - Public / / koa - static static database index, js β”œ ─ SRC β”œ ─ API / / network request code, tools function and related configuration β”œ ─ assets / / font configuration and global style β”œ ─ baseUI / / β”œβ”€ β”œβ”€pages // β”œβ”€routes // β”œβ”€ β”œβ”€ exercises // exercises // exercises // exercises // exercises // exercises // exercises // Exercises // Exercises // Exercises // Exercises // Exercises // Exercises // Exercises // Exercises // Exercises // Exercises // Exercises // Exercises // Exercises // Exercises // Exercises // Exercises // Exercises // Exercises // Exercises // Root component main.jsx // entry fileCopy the code

Under each page directory, there is a store folder, which is the sub-warehouse of the page. During data management, all the data in the sub-warehouse will be merged into the main warehouse, so that each page can only manage the data under this page.

Specific parts of the project

The routing configuration

To create the React project, use the commands to create the project scaffolding

npm init @vitejs/app appName --template react
Copy the code

First configure the route in index.js of routes

.import React, { lazy, Suspense } from 'react';
const Main = lazy(() = > import('.. /pages/Main/Main')); .const SuspenseComponent = Component= > props= > {
    return (
        <Suspense fallback={null}>
            <Component {. props} ></Component>
        </Suspense>)}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/server'.// Encapsulate the SuspenseComponent function to dynamically route the SuspenseComponent, and load the corresponding component only when switching to the corresponding route
                    component: SuspenseComponent(Server),
                },
                
                ......
           
            ]
        },
        {
                path: '/detail/:id'.component: SuspenseComponent(Detail),
        }
    ]
}]
Copy the code

Lazy +Suspense optimizations are used during routing configuration. All components are loaded in through lazy loading, so there may be delays in rendering pages, but with Suspense you can optimize interactions.

Render subordinate routes using renderRouter

In order for routes to take effect, use renderRoutes where child routes need to be enabled in app.js

import React from 'react';
// renderRoutes reads the Route configuration and converts it to the Route tag
import { renderRoutes } from 'react-router-config';
import { BrowserRouter } from 'react-router-dom';

// Shell components for all components
function App() {
  return (
     <Provider store={store}>
      <div className="App">
        <BrowserRouter>
          {renderRoutes(routes)}
        </BrowserRouter>
      </div>
    </Provider>)}export default App;
Copy the code

The browser route is used here, leaving no # in the URL, which is much nicer than the hash route.

At the same time, store warehouse is provided in the outermost layer of app.js, so that each route can extract data from the general warehouse.

Click here for the full code

Next comes the level 1 routing of the project

The contents of the first level route can be seen on every page, so write a Tabbuttom component above the first level route, put it at the bottom of the page, and then change the route in the Tabbuttom link, display different components to achieve the effect of changing the page. {renderRoutes(route.routes)} must be written inside the component to render the routes to be displayed. Here is part of the core code:

.const Bottom = (props) = >{...return (
        <>} {renderRoutes(route.routes)}<ul className="Botton-warper">
                <li
                    onClick={()= > { changeIndex(0) }}
                    className="Botton-warper-warp"
                    key="1">
                    <Link to="/home/main"
                        style={{ textDecoration: "none}} ">
                        <div>
                            <div className="icon" style={index= = =0 ? { display: "none"}:{}} >
                                <img src={main} alt="" />
                            </div>
                            <div className="icon1" style={index= = =0 ? {} : { display: "none}} ">
                                <img src={main} alt="" />
                            </div>
                            <div
                                className="planet">Home page</div>
                        </div>
                    </Link>
                </li>.</ul>
        </>)}Copy the code

Data Flow management

After solving the primary routing can begin to write the inside of the secondary routing component, so roughly shape came out of the project, but there is a very important thing is not done, the data flow management, management of data streams, I think three yuan greatly small volume inside write very good, I am also according to the three big data stream management to write this project, If you are interested, you can go and have a look at this booklet. (juejin. Cn/book / 684473…).

In data flow management, there is a master repository in the Store, which merges all the sub-repositories through redux-thunk.

store/reducer.js

import thunk from 'redux-thunk';
import { createStore, compose, applyMiddleware, combineReducers } from 'redux';
import reducer from "./reducer";
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(reducer, composeEnhancers(applyMiddleware(thunk)));
export default store;
Copy the code

store/index.js

import { combineReducers } from 'redux';
import { reducer as mainReducer } from '.. /pages/Main/store/index'
import { reducer as serverReducer } from '.. /pages/server/store/index'
import { reducer as myReducer } from '.. /pages/my/store/index'
import { reducer as infoReducer } from '.. /pages/info/store/index'

export default combineReducers({
    main: mainReducer,
    server: serverReducer,
    my:myReducer,
    info:infoReducer
});
Copy the code

Once the Redux repository is set up, connect the two repositories in the provider package component so that the data in the repository can be used.

Here is the structure of one of the pages:

The constants file is used to write the name of dispatch, actionactionine.js to work with the data that is sent, and reducer.js to work with the data that is returned.

//reducer.js

import * as actionTypes from './constants';

const defaultstate = {
    maindata: [].num: 9.index: 0  // Tabbar which is active? The core status
}

const reducer = (state = defaultstate, action) = > {
    switch (action.type) {
        case actionTypes.SET_INDEX:
            return{... state,index: action.data}
        case actionTypes.SET_NUM:
            return{... state,num: action.data }
        case actionTypes.CHANGE_MAINDATA:
            return{... state,maindata: action.data }
            // console.log(state,'88888888888888888888');
            return state;

        default:
            returnstate; }}export default reducer;
Copy the code
//actionCreators.js

export const getMainData = () = > {
    return (dispatch) = > {
        reqmain()
        
            .then((res) = > {
                console.log(res);
                dispatch(changeMainData(res.data.data))
            })
            .catch((e) = > {
                console.log('Wrong'); }}})Copy the code

Once connected to the database, you can start writing pages that add functionality based on this template:

import { connect } from 'react-redux'

const Main = (props) = > {
    const { getMainDataDispatch } = props
    const { maindata } = props
    
    return (
        <>.</>)}const mapStateToPorps = (state) = > {
    return {
        maindata: state.main.maindata
    }
}
const mapStateToDispatch = (dispatch) = > {
    return {
        getMainDataDispatch() {
            dispatch(actionTypes.getMainData())
        }
    }
}

export default connect(mapStateToPorps, mapStateToDispatch)(Main)


Copy the code

Mobile adaptation

Mobile adaptation using the postCSs-px-to-viewPort plugin to automatically convert PX to VW

npm install postcss-px-to-viewport --save-dev
Copy the code
  1. Parameter configurationpostcss.config.js
"Postcss-px-to-viewport ": {// options unitToConvert: "px", // the unitToConvert is viewportWidth: 750, // the design interface width unitPrecision: PropList: ["*"], // List of vw attributes that can be converted viewportUnit: "vw", // fontViewportUnit: "Vw ", // The viewport unit used for the font selectorBlackList: [], // The CSS selector to ignore minPixelValue: 1, // Set the minimum conversion value, if it is 1, only values greater than 1 will be converted mediaQuery: Exclude: [], landscape: false, landscapeUnit: "Vw ", // unit landscapeWidth used when landscapeWidth: 568 // Viewport width used when landscapeWidth}Copy the code

So it fits, and it’s ready to go.

Page development

First, here’s the home page:

Since this is a mobile project, there is always a Scroll component in the middle of the page that allows the page to scroll down like a mobile phone

There are three different kinds of articles, and the order of the articles is random, so you need to use MockJS in the background to generate three different kinds of data, and then use Map to loop out three different kinds of articles.

Data is requested 20 at a time, when browsing to the last data, then go to the background to request data, so that data can always be displayed, which requires the use of uesEffect to monitor page data, when the number of page changes, again to the background to request data, display the article. Part of the core code is as follows:


const Main = (props) = > {
    const fetchText1 = () = > {
        api.reqlist(page)
            .then(res= > {
                settext1([
                    ...text1,
                    ...res.data.data.text1
                ])     
            })
    }
    return (
        <>
            <div className="main">
                <Title />
                <Scroll
                    ref={scrollref}
                    direction={"vertical"} / /refresh={false}
                    refresh={true}
                    onScroll={
                        (e) = > {
                            forceCheck()
                        }
                    }
                    pullUp={handlePullUp}
                     >
                    <div className="main-padding" >       
                        <SearchInput handleOnclick={()= > { handleOnclick() }}
                            searchBoxHandleOnclick={() => history.push('/search')} />
                        <Swipers rotationImg={rotationImg} />
                        <ImgList />
                        <ListData text1={text1} />
                    </div>
                </Scroll>

            </div>
        </>)}Copy the code

Next is the car circle page:

In order to implement the comment function, we use two libraries, one is momont library, which is used to convert the time, you can see how long ago the user commented after the comment, and the other is due to LokiJS library, LokiJS is a pure JavaScript implementation of in-memory database, document-oriented, So you can use LokiJS to store comments.


const Info = (props) = > {


  return (
    <>
      <div className="main" onClick={()= > { console.log('father') }}>

        <Title />
        <div className="main-padding" >
          <div className="nav">
            {
              infodata.map((item, index) => {
                return (
                  <div className="nav-items" key={item.id} onClick={()= > change_id(item.id)}>
                    <div className="items2"></div>
                  </div>)})}</div>
        </div>

        <Commonts className='father' commontsindex={commontsindex} id={console.log('commontstheidIs',commontsindex)} commonts={commontslist} />
      </div>
    </>)}export default connect(mapStateToProps, mapStateToDispatch)(Info)

Copy the code

Still optimizing…… See this

Finally made a simple login and logout interface, this is to use localStorage to store the login information, determine whether logged in, when there is a login state automatic login, not detailed.

Build the backend using KOA

In the process of building the background, the required data is transmitted to index.js in JSON format, mockJS is used to simulate background data, Koa-CORS is used to solve cross-domain problems, and KoA-static is used to transmit static resources.

const Koa = require('koa')
const router = require('koa-router') ();const app = new Koa()
const cors = require('koa2-cors')
const Mock = require('mockjs')
const Random = Mock.Random
app.use(require('koa-static') ('./Public'));
// const querystring = require('querystring');

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://121.40.130.18:5678'. // 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
}))
router.get('/home/main'.async (ctx) => {
    ctx.response.body = {
        success: true.data: MainData
    }
})

router.get('/home/list'.async (ctx) => {

    let {
        limit = 40, page = 1
    } = ctx.request.query
    console.log(limit, page, "# # #");
    console.log(ctx.request.query, "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -");
    // Filter data according to limit and page

    // Parameter page limit
    let data = Mock.mock({
        'text1|20': [{
            'id': "@increment"."title": "@ ctitle (15, 20)".'writer': '@ ctitle (3, 5)'.'time': '@time(MM-dd HH:mm)'.'imgsrc1': '@img(110x80)'.'imgsrc2': '@img(110x80)'.'imgsrc3': '@img(110x80)'.'imgsrc4': '@img(110x120)'.'type|1': Random.range(1.4.1),
        }],
    })
    ctx.body = {
        success: true,
        data
    }
})
    ctx.body = {
        success: true.data: data2
    }
})


app
    .use(router.routes())
    .use(router.allowedMethods())
// 1. HTTP service
// 2. Simple routing module
// 3. cors
// 4. Return data

app.listen(5678.() = > {
    console.log('server is running in port 5678')})Copy the code

To optimize the

Use the React. Memo

Components are the basic unit that makes up the React view. Some components have their own local states, and when their values change due to user action, the components are rerendered. In a React application, a component might be rendered frequently. A few of these renders are necessary, but most are useless, and their presence can significantly degrade the performance of our application.

React. Memo will return a purified MemoFuncComponent, which will be rendered in the JSX tag. React checks if the props and state of the component are the same as the next. If they are the same, the component will not be rendered; if they are different, the component will be rendered again.

export default React.memo(Main)
Copy the code

Lazy loading of images

Use the React-LazyLoad library for lazy image loading

import Lazyload from 'react-lazyload'

 <Lazyload height={100} placeholder=
 {
    <img width="100%" height="100%"
        src={loading}
    />
}>
   <div className="ListItem-content__img1">
        <img src={item.imgsrc3} alt="" />
   </div>
</Lazyload>

Copy the code

alias

When import React occurs in our code, Webpack will recursively search up to node_modules. In order to reduce the search scope, we can directly tell Webpack to go to that path, which is the alias configuration

import * as api from '@/api'
Copy the code
resolve: {
    alias: {
      The '@': path.resolve(__dirname, 'src')}}Copy the code

conclusion

Learn how to write a React project. Learn how to write a React project. Learn how to write a React project and learn how to do it.

The source code

  • Gitee address

  • Online address (mobile terminal project, remember to use the simulator to view.)