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
- Parameter configuration
postcss.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.)