preface

After learning React for some time, spring recruitment is coming. Now I find that I need a small project to summarize and apply what I have learned. Is the so-called hundred learning is better than a practice, actual combat out of the truth πŸ‘€.

Project introduction

  • Technology stack: React Hooks + Redux + Koa
  • React Hooks are used to write the front-end page, Koa is used to build the background, Redux is used to manage the data flow, styled components are used to write the style for the whole project, and React-Router V5 is used to write the routing configuration
  • Stick to the design concept of back-end MVC and front-end MVVM, and follow the thought of componentization and modular programming

The results of

The project structure

β”œ ─ server/ / the back endβ”œ ─ Data/ / dataThe index, js β”œ ─ SRC β”œ ─ API// Network request code, utility class functions, and related configurationsβ”œ ─ assets// Font configuration and global styleβ”œ ─ baseUI// Base UI wheelβ”œ ─ components// Reusable UI componentsβ”œ ─ layouts/ / layoutβ”œ ─ pages/ / pageβ”œ ─ routes// Route configuration fileβ”” ─ store// Redux files
      App.jsx               / / the root component
      main.jsx              // Import file
Copy the code


The front part

The routing configuration

In this project, react-router-config is used to configure routes.

  • Routes /index.js
import React, { lazy, Suspense } from 'react';
import HomeLayout from '.. /layouts/HomeLayout';
import { Redirect } from 'react-router-dom';

const Tesla = lazy(() = > import('.. /pages/Tesla'));
const Find = lazy(() = > import('.. /pages/Find')); .const SuspenseComponent = Component= > props= > {
    return (
        <Suspense fallback={null}>
            <Component {. props} ></Component>
        </Suspense>)}export default [{
    path: "/".component: HomeLayout,
    routes: [{path: '/'.exact: true.render: () = > < Redirect to={"/tesla"}, {} / >,path: "/tesla".component: SuspenseComponent(Tesla),
            routes: [{path: '/tesla/car/:id'.component: SuspenseComponent(Model)
                },
                {
                    path: '/tesla/order'.component: SuspenseComponent(Order)
                }
            ]
        }
    ]
}]
Copy the code
  • Render subordinate routes using renderRouter

For routes to take effect, use renderRoutes where child routes need to be enabled

Here is the 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 (
    <div className="App">
      <BrowserRouter>
        {renderRoutes(routes)}
      </BrowserRouter>
    </div>)}export default App;
Copy the code


Data flow management Redux

Routing has been configured, the next page to write, is the so-called soldiers did not move, provisions first. My personal preference is to manage the data flow before the page is written

I won’t go into too much detail here about how Redux works. In this project, we split the Reducer, each page had a reducer function that independently managed state, and then merged it into a total Reducer.

  • The store/reducer.js code is as follows
import { combineReducers } from 'redux';
import { reducer as TeslaReducer } from '.. /pages/Tesla/store';
import { reducer as ShopReducer } from '.. /pages/Shop/store'
import { reducer as FindReducer } from '.. /pages/Find/store';

export default combineReducers({
    tesla: TeslaReducer,
    shop: ShopReducer,
    find: FindReducer
});
Copy the code

We then create a store with this total reducer, and every time we dispatch an action on the store, the data in the store changes accordingly.

/** * This file is specifically used to expose a store object, the entire application has only one store object */
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 general store has been created. Take the Store of Tesla sub-page as an example, the structure is

β”œβ”€Tesla β”œβ”€ Store ActionOffers.js constants.js index.js reducerCopy the code
  • Tesla/store/reducer.js

This file is a reducer used to create a Tesla component service. The essence of reducer is a pure function. The reducer function receives two parameters, namely, previous state and action.

import * as actionTypes from './constants';

// Initialization state
const defaultstate = {
    tesladata: [].// Record tabbar click highlight
    index: 2.// Record the selected color of the detail page
    colorIndex: 0.// Record the style selected by the detail page wheel
    wheelIndex: 0
}

const reducer = (state = defaultstate, action) = > {
    // How to process data according to type
    switch (action.type) {
        case actionTypes.CHANGE_TESLADATA:
            return { ...state, tesladata: action.data }
        case actionTypes.CHANGE_INDEX:
            return { ...state, index: action.data }
        case actionTypes.SET_COLORINDEX:
            return { ...state, colorIndex: action.data }
        case actionTypes.SET_WHEELINDEX:
            return { ...state, wheelIndex: action.data }
        default:
            returnstate; }}export default reducer;
Copy the code
  • Tesla/store/constants.js
export const CHANGE_TESLADATA = 'tesla/CHANGE_TESLADATA';
export const CHANGE_INDEX = 'tesla/CHANGE_INDEX';
export const SET_COLORINDEX = 'teslaInfo/model/SET_COLORINDEX';
export const SET_WHEELINDEX = 'teslaInfo/model/SET_WHEELINDEX';
Copy the code
  • Tesla/store/actionCreators. Js code

Once the Remain method gets the data from the back end, Dispatch (the action returned by the changeMianData function) will modify the data on the Tesla page.

import * as actionType from './constants.js';
import { reqmain } from '.. /.. /.. /api/index'

export const changeMainData = (data) = > {
    return {
        type: actionType.CHANGE_TESLADATA,
        data: data
    }
}

// The getMainData is available directly from import {actionCreators} from './store'
export const getMainData = () = > {
    / / API request
    // Dispatch a synchronization task
    return (dispatch) = > {
        reqmain()
            .then((res) = > {
                // console.log(res);
                dispatch(changeMainData(res.data.data))
            })
            .catch((e) = > {
                console.log('Wrong'); }}})Copy the code
  • API /index.js part of the code
// Ajax is wrapped
export const reqmain = () = > {
    // GET is passed by default
    return Ajax('/tesla')}Copy the code

Once the Redux repository is set up, there are a few additional tools needed to use Redux and React together, the most important of which is React-Redux.

React-redux provides two important objects, Provider and Connect. The former makes the React component connectable, and the latter actually connects the React component to redux’s store. Use the Provider wrapper in the outermost container, passing the created store as an argument to the Provider.

import {Provider} from 'react-redux';

function App() {
  return (
    <Provider store={store}>
      <div className="App">
        <BrowserRouter>
          <Tesla></Tesla>
        </BrowserRouter>
      </div>
    </Provider>)}export default App;
Copy the code

Do not forget to use connect for all components wrapped in the Provider to use the numbers in the Store.

import React from 'react';

function Tesla() {
  return (
    <div></div>
  );
}
// This function allows us to bind the data in the store to the component as props
const mapStateToPorps = (state) = > {
  return {
    tesladata: state.tesla.tesladata
  }
}

// This function binds action as props to Main.
const mapStateToDispatch = (dispatch) = > {
  return {
    getMainDataDispatch() {
      dispatch(actionCreators.getMainData())
    }
  }
}

export default connect(mapStateToPorps, mapStateToDispatch)(memo(Tesla))
Copy the code

At this point, we can use the data from the back-end interface in the Redux repository for each child page. Now that you have the data, you can start writing the page.


Page to write

Tesla page


My back-end data are all in JSON format. In fact, using two useState to determine colorIndex and wheelIndex respectively can realize the matching of different tires and colors. But since car configuration can be used on multiple pages, I use Redux data flow management for configuration information.

The setCarColorIndex method is triggered by clicking the color pictures in a circular arrangement. The index of the color is passed in as index and the colorIndex is changed. The wheel hub matching principle is the same. Therefore, the matching also has the memory function. After exiting the matching, there is still the matching information just now.

  • TeslaInfo/Model/index.jsx
<div className="carColor">
    {
        color.map((item, index) = > {
            return (
                <div key={item.id} className={colorIndex= =item.id - 1 ? "colorImg" :""} >
                    <img src={item.picUrl} onClick={()= > {setCarColorIndex(item.id - 1)}} />
                </div>
            )
        })
    }
</div>

setCarColorIndex(index) {
    // The method setColorIndex in actionfiles.js
    dispatch(actionCreators.setColorIndex(index))
}
Copy the code

The code is here

Find page and Map page


Found page logic to use the pull down refresh, here amway a wave of god three large scroll component, super easy to use 😎, bought the nuggets of the booklet can see oh

  • The Find/index.jsx section is as follows

HandlePullUp is triggered when it is pulled down, ++page

GetFindDataDispatch (); // Determine whether the repository is empty. If it is empty, getFindDataDispatch() changes the page and re-renders the page
useEffect(() = > {
  if(! finddata.length) { getFindDataDispatch(page)setTimeout(() = > {
      setIsLoading(false)},500)
  }
}, [page])

// Pull up to load more
const handlePullUp = () = > {
  if (isLoading) return;
  setPage(++page)
  setIsLoading(true)}const mapStateToDispatch = (dispatch) = > {
  return {
    getFindDataDispatch(page) {
      dispatch(actionCreators.getFindData(page))
    }
  }
}
Copy the code

The code is here

  • API /index.js part of the code
export const reqfind = (page) = > {
    return Ajax(`/find/${page}`)}Copy the code

Page changes rerender the page, and reqFind will fetch the new data from the back end through the new page.

  • The Find/store/actionCreators. Js code
export const getFindData = (page) = > {
  return (dispatch) = > {
    reqfind(page)
      .then((res) = > {
        dispatch(changeFindData(res.data.data))
      })
      .catch((e) = > {
        console.log('Wrong'); }}})Copy the code

About Tesla map making here is just a simple implementation of the location function, to be continued… See this

Shop page


Shop page t-zone Swiper3 Slides center + automatic grouping templates

  • The code for Shop/index.jsx is shown below
  setTimeout(() = > {
    new Swiper('.swiper-container', {
      slidesPerView: 'auto'.centeredSlides: true.paginationClickable: true.spaceBetween: 20
    });
  }, 100)
  <div className="swiper-container">
    <p>T - ZONE</p>
    <div className="swiper-wrapper">
      {
        TZONE.map((item, _) => {
          return (
            <div key={item.id} className="swiper-slide">
              <img src={item.picUrl} onClick={()= > goDetail(item)}/>
            </div>)})}</div>
  </div>
Copy the code

The code is here

Click on the Tesla store to get to the product details page

The floating navigation bar at the top of the item can appear in different styles as the screen scrolls. Here, two useState are used, starting with two main colors. Change state by listening for the onScroll property in the super useful Scroll component. The color will also change.

  • ShopInfo TeslaShop/index. The JSX part code is as follows
  const [currentColor, setCurrentColor] = useState('white')
  const [currentBgColor, setCurrentBgColor] = useState('#2B2D2E')
  
  const showHeader = (e) = > {
    if (e.y < -90) {
      setCurrentColor('black')
      setCurrentBgColor('white')}else {
      setCurrentColor('white')
      setCurrentBgColor('#2B2D2E')
    }
  }
  
  <div className="shop-header" style={{ backgroundColor: `${currentBgColor}`.color: `${currentColor}`}} ><div className="shop-header-left">
      <svg viewBox="0 0 342 to 35" xmlns="http://www.w3.org/2000/svg"><path d="..." fill={currentColor}></path></svg>
      <span>|</span>
      <span>The store</span>
    </div>
    <div className="shop-header-right">
      <svg viewBox="0 0 1024 1024" p-id="7071"><path d="..." p-id="7072" fill={currentColor}></path></svg>
      <span>The navigation bar</span>
    </div>
  </div>
Copy the code

The code is here

Style components, styled- Components

React style-components is a set of CSS in JS framework written for React. The advantage of CSS in JS over the preprocessor (Sass, less) is that CSS in JS uses THE JS syntax, and there is no need to relearn new technology, and there is no additional compilation step. It will certainly speed up your web pages.

The basic usage is not difficult either, simply importing styles in the form of components.

  • Tesla/index.js
import { Main } from './index.style';
return (
  <Main> 
  </Main>
)
Copy the code
  • Tesla/index.style.js
import styled from 'styled-components'

export const Main = styled.div`... `
Copy the code


To optimize the

Route lazy loading

In order to improve user experience, we use React lazy and Suspense to load routes lazily.

The react. lazy function lets you render dynamically imported components as regular components.

After React uses lazy, there will be a gap in loading. React doesn’t know what to show in Suspense during this gap, so we need to specify it.

After App rendering is complete and the module containing Tesla has not been loaded, we can use Suspense to gracefully degrade the component imported for react. lazy, also known as Tesla.

import React, { lazy, Suspense } from 'react';

// Use React. Lazy to import the Tesla component
const Tesla = lazy(() = > import('.. /pages/Tesla'));

// Packaged SuspenseComponent component, ready-to-use package
const SuspenseComponent = Component= > props= > {
    return (
        <Suspense fallback={null}>
            <Component {. props} ></Component>
        </Suspense>)} {path: "/tesla".// Just wrap the component around it
    component: SuspenseComponent(Tesla)
}
Copy the code

Page rendering optimized Memo

If your function component is rendering the same results given the same props, you can improve the performance of the component by wrapping it in a react.Memo call to remember the results of the component’s rendering. This means that in this case React will skip the rendering component and simply reuse the last render.

Use React. Memo to prevent unnecessary components from being re-rendered as the page updates

import memo from "react";

const main = () = > {}

export default memo(Main)
Copy the code

Lazy loading of images

React-lazyload is introduced to implement lazy loading of images

import Lazyload from 'react-lazyload'

<div className="newsRight">
  <Lazyload
    height={100}
    placeholder={
      <img width="100%" height="100%" src={loading} />
    }
  >
    <img src={item.picUrl} />
  </Lazyload>
</div>
Copy the code


The backend part

Koa to set up the back end

  • The server/idnex.js code is as follows
const fs = require('fs')
const TeslaData = require('./Data/teslaData/TeslaData.json')

const Koa = require('koa');// Introduce the KOA module
const router = require('koa-router') ();// Introduce koa-router and instantiate it (omit new)
const app = new Koa();/ / instantiate
// Configure the route
router.get('/tesla'.async (ctx) => {
    ctx.response.body = {
        success: true.data: TeslaData
    }
})

app
    .use(router.routes())// Start the route
    .use(router.allowedMethods())// Routes can be configured

app.listen(9000.() = > {
    console.log('server is running 9000');
})
Copy the code


Cross domain

  • The server/idnex.js code is as follows
const cors = require('koa2-cors')

app.use(
    // Allow cross-domain
    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


Project encountered potholes

TabBar icon and text do not match after refreshing the page

After the page is refreshed, the state of the image is lost. Only the isActive value of className in NavLink is saved, so the text state is unchanged, but the state of the selected image is returned to the initial value.

Solution: Handle direct access based on user routing, not the home page

import { useLocation } from 'react-router-dom';

const { pathname } = useLocation()

index = route.routes.findIndex(item= > item.path == pathname)
Copy the code

The configuration information is lost after the page is refreshed

Redux-persist allows you to persist local data stores in redux and whitelists the stores that need to be kept.

import thunk from 'redux-thunk';
// createStore is introduced to create the core store object in Redux
import { createStore, compose, applyMiddleware } from 'redux';
// Import the reducer for the component services
import reducer from "./reducer";

// redux-persist implements redux persistence to local data stores.
import {persistStore, persistReducer} from 'redux-persist';
// The storage mechanism can be changed to another mechanism, currently using the sessionStorage mechanism
import storageSession from 'redux-persist/lib/storage/session'
// import storage from 'redux-persist/lib/storage'; / / localStorage mechanism

const storageConfig = {
    key: 'root'.// It must be
    storage:storageSession, // Cache mechanism
    // Blacklist: ['index'] // All data that are not persistent in the reducer are persistent data
    // Must be like store, cannot be a property
    whitelist: ['tesla'] // Persistent data in the reducer is non-persistent except for other data
}

const myPersistReducer = persistReducer(storageConfig, reducer)
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(myPersistReducer, composeEnhancers(applyMiddleware(thunk)));
export const persistor = persistStore(store)
/ / store
export default store;
Copy the code


conclusion

This big DEMO and small project can be regarded as a summary of my 21 years of learning React. The functions are still incomplete, but for me, it is a small step from theory to practice. Learn from hooks if you need to be familiar with the syntax and application scenarios. Welcome to dig friends a lot of praise oh!! πŸ‘€


The source code

  • Project address: Github, Gitee
  • The online address project is still being updated and iterated