React is a popular framework for front-end development in recent years. It is supported by the Facebook team and enjoys a high market share. Many beginners struggle with react or Vue to start with. Personally, if you have time, you’d better master both. React is a little harder to learn than Vue. Starting with react is difficult, but learning react is a great way to improve your front-end skills. This article gives a detailed overview of react creation, streamlining projects, configuring and debugging environments, and integrating various related tools step by step. It focuses on quickly building standard React projects, which can save a lot of time for newcomers to explore.

In February 2020, I released a super comprehensive and Detailed Tutorial! Build React Project From Scratch. After two years of technical iterations, I updated this version. This tutorial uses the latest version of the technology, with chapters tweaked and supplemented to make it easier to understand, as well as fixing bugs in older versions. Please follow the new version of the tutorial step by step operation.

The Demo depends on the following package versions:

Node. Js 16.13.2

The create – react – app 5.0.0

The react 17.0.2

The react – the router – dom 6.2.1

Antd 4.18.7

Node – sass 7.0.1

Sass – loader 12.3.0

Less 4.1.2

Less – loader 10.2.0

Stylus 0.56.0

Stylus – loader 6.2.0

Axios 0.26.0

The history 4.10.1

Immutable 4.0.0

Mockjs 1.1.0

The react – redux 7.2.6

Redux 4.1.2

Story – immutable 4.0.0

Story – thunk against 2.4.1

Babel – plugin – import 1.13.3

The HTTP proxy – middleware 2.0.3

A sneak peek

Take a look at the table of contents to see what’s in this tutorial.

1 Initializing a project • 1.1 Creating a project using create-React-app 5.0 • 1.2 Thin Provisioning project 2 Webpack Configuration • 2.1 Exposing Webpack • 2.2 Sass/Scss • 2.3 Less • 2.4 Support for Stylus • 2.5 Setting path aliases • 2.6 Forbid build projects to generate map files 3 Project architecture building • 3.1 Project directory structure Design • 3.2 Setting global common styles • 3.3 About style naming conventions 4 Introduce Ant Design • 4.1 Install Ant Design • 4.2 Load on demand • 4.3 Set Antd to Chinese • 4.4 Customize Antd theme color (optional) 5 Page development • 5.1 Build login page • 5.2 Build Home page • 5.3 Implement page route jump • 5.4 Redirecting pages in the React component • 5.5 Redirecting pages in non-React Components 6 Component development • 6.1 Creating the Header component • 6.2 Importing the Header component • 6.3 Uploading Parameters to the Component 7 React Developer Tools browser plug-in 8 Redux and related plug-ins • 8.1 Installing Redux • 8.2 Installing React-Redux • 8.3 Installing Redux-thunk • 8.4 Installing Redux browser plug-in • 8.5 Creating a Store • 8.6 Store decomposing • 8.7 Installing immutable • 8.8 Interconnecting with React-Redux and Store • 8.9 Setting and reading REdux variables in real time on the Login page • 8.10 Reading Redux variables in real time on the Header component • 9 Encapsulating the common API library based on AXIOS • 9.1 Installing AXIOS • 9.2 Encapsulating the Common API Library • 9.3 Mocking. js installation and use • 9.4 Initiating API requests • 9.5 Setting up reverse proxy requests for the development environment 10 Build project 11 Project Git source code conclusionCopy the code

Does note:

At the beginning of each line in the code area:

“+” indicates new data

“-” indicates deletion

“M” indicates modification

Even if you are new to the React project, you can learn how to React quickly.

1 Initialize the project

1.1 Create a project using create-react-app 5.0

To find an appropriate directory, execute:

npx create-react-app react-app5
Copy the code

The react-app5 at the end of the command is the name of the project, which can be changed.

Create-react-app has released 5.0.0 at the time of writing this tutorial.

You are running create-react-app 4.0.3 which is behind the latest release (5.0.0)Copy the code

If you are using create-react-app, you need to clear the NPX cache and run the following command:

npx clear-npx-cache
Copy the code

Then run NPX create-react-app react-app5 to create the project.

Wait a moment to complete the installation. After the installation is complete, you can start the project using NPM or YARN.

Go to the project directory and start the project:

CD react-app5 yarn start (or use NPM start)Copy the code

If yarn is not installed, you can go to the Yarn Chinese website to install yarn:

yarn.bootcss.com/

Once started, projects can be accessed at the following address:

http://localhost:3000/

1.2 Thin Projects

Next, simplify the project by removing files that are not used in the general project.

├ ─ / node_modules ├ ─ / public | ├ ─ the favicon. Ico | ├ ─ index. The HTML - | ├ ─ logo192. PNG - | ├ ─ logo512. PNG - | ├ ─ mainfest. Json - | └ ─ robots. TXT ├ ─ / SRC - | ├ ─ App. CSS | ├ ─ App. Js - | ├ ─ App. Test, js - | ├ ─ index. The CSS | ├ ─ but js - | ├ ─ logo. The SVG - | ├ ─ reportWebVitals. Js - | └ ─ setupTests. Js ├ ─. Gitignore ├ ─ package. The json ├ ─ README. Md └ ─ yarn. The lockCopy the code

The directory structure is now much cleaner:

├ ─ / node_modules ├ ─ / public | ├ ─ the favicon. Ico | └ ─ index. The HTML ├ ─ / SRC | ├ ─ App. Js | └ ─ index. The js ├ ─. Gitignore ├ ─ Package. json ├─ readme. md ├─ map.lockCopy the code

An error message will be displayed after the above files are deleted. This is because the corresponding file reference no longer exists. You need to keep modifying the code to get the project up and running.

Modify the following files one by one:

SRC/App. Js:

function App() {
    return <div className="App">React-App5</div>
}

export default App
Copy the code

SRC/index. Js:

import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'

ReactDOM.render(<App />, document.getElementById('root'))
Copy the code

Public/index. HTML:

<! DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <link rel="icon" href="%PUBLIC_URL%/favicon.ico" /> <meta  name="viewport" content="width=device-width, initial-scale=1" /> <title>React App</title> </head> <body> <noscript>You need to enable JavaScript to run this app.</noscript> <div id="root"></div> </body> </html>Copy the code

The running effect is as follows:

2 Webpack configuration

2.1 exposed webpack

Create-react-app does not expose configuration files by default. To configure projects flexibly, you need to expose configuration files.

Run the following command to expose the configuration file:

yarn eject
Copy the code

Eject must ensure that all files in the current project have been committed to Git, otherwise the following error will be reported:

Remove untracked files, stash or commit any changes, and try again.
Copy the code

You need to execute it in the project root directory first, and submit git:

Git add. Git commit -mCopy the code

Then execute:

yarn eject
Copy the code

The webpack exposure is complete, and there are many more files in the project.

2.2 support Sass/Scss

After eject, although there is sass code in package.json and webpack.config.js, to use sass /Scss correctly, node-sass needs to be installed again.

First set the Taobao image of Node-sass, download node-sass faster:

yarn config set SASS_BINARY_SITE http://npm.taobao.org/mirrors/node-sass
Copy the code

If you have not set the taobao image of the regular Registry, set it as well:

yarn config set registry https://registry.npm.taobao.org/
Copy the code

Run the following command:

yarn add node-sass --dev
Copy the code

After the installation, the project supports Sass/Scss.

2.3 support Less

Install Less and less-loader:

yarn add less less-loader --dev
Copy the code

Then modify config/webpack.config.js:

// style files regexes const cssRegex = /\.css$/; const cssModuleRegex = /\.module\.css$/; const sassRegex = /\.(scss|sass)$/; const sassModuleRegex = /\.module\.(scss|sass)$/; + const lessRegex = /\.less$/; + const lessModuleRegex = /\.module\.less$/; . // Opt-in support for SASS (using.scss or.sass extensions). // By default we support SASS Modules with the // extensions .module.scss or .module.sass { test: sassRegex, exclude: sassModuleRegex, use: getStyleLoaders( { importLoaders: 3, sourceMap: isEnvProduction && shouldUseSourceMap, }, 'sass-loader' ), // Don't consider CSS imports dead code even if the // containing package claims to have no side effects. // Remove this  when webpack adds a warning or an error for this. // See https://github.com/webpack/webpack/issues/6571 sideEffects: true, }, // Adds support for CSS Modules, but using SASS // using the extension .module.scss or .module.sass { test: sassModuleRegex, use: getStyleLoaders( { importLoaders: 3, sourceMap: isEnvProduction && shouldUseSourceMap, modules: {getLocalIdent: getCSSModuleLocalIdent,},}, 'pass-loader'),}, + // exclude: lessModuleRegex, + use: getStyleLoaders( + { + importLoaders: 3, + sourceMap: isEnvProduction + ? shouldUseSourceMap + : isEnvDevelopment, + modules: { + mode: 'icss', + }, + }, + 'less-loader' + ), + sideEffects: true, + }, + { + test:lessModuleRegex, + use: getStyleLoaders( + { + importLoaders: 3, + sourceMap: isEnvProduction + ? shouldUseSourceMap + : isEnvDevelopment, + modules: { + mode: 'local', + getLocalIdent: getCSSModuleLocalIdent, + }, + }, + 'less-loader' + ), + },Copy the code

Copy the sASS configuration code and change it to less. After the preceding operations, the project supports less.

2.4 support Stylus

Support Stylus exactly the same as Less, first install Stylus and stylus-loader:

Run the following command:

yarn add stylus stylus-loader --dev
Copy the code

Once the installation is complete, modify config/webpack.config.js as described in the previous section to support less:

// style files regexes const cssRegex = /\.css$/; const cssModuleRegex = /\.module\.css$/; const sassRegex = /\.(scss|sass)$/; const sassModuleRegex = /\.module\.(scss|sass)$/; const lessRegex = /\.less$/; const lessModuleRegex = /\.module\.less$/; + const stylusRegex = /\.styl$/; + const stylusModuleRegex = /\.module\.styl$/; . + // Support stylus + {+ test: stylusRegex, + exclude: stylusModuleRegex, + use: getStyleLoaders(+ {+ importLoaders: 3, + sourceMap: isEnvProduction + ? shouldUseSourceMap + : isEnvDevelopment, + modules: { + mode: 'icss', + }, + }, + 'stylus-loader' + ), + sideEffects: true, + }, + { + test:stylusModuleRegex, + use: getStyleLoaders( + { + importLoaders: 3, + sourceMap: isEnvProduction + ? shouldUseSourceMap + : isEnvDevelopment, + modules: { + mode: 'local', + getLocalIdent: getCSSModuleLocalIdent, + }, + }, + 'stylus-loader' + ), + },Copy the code

After following the above steps, the project already supports Stylus.

2.5 Setting a Path alias

To avoid the hassle of using relative paths, you can set path aliases.

Modify the config/webpack. Config. Js:

alias: { // Support React Native Web // https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/ 'react-native': 'react-native-web', // Allows for better profiling with ReactDevTools ... (isEnvProductionProfile && { 'react-dom$': 'react-dom/profiling', 'scheduler/tracing': 'scheduler/tracing-profiling', }), ... (modules.webpackAliases || {}), + '@': path.join(__dirname, '.. ', 'src'), },Copy the code

SRC root = SRC root; SRC root = SRC root; SRC root = SRC root; / “.

For example, SRC /app.js:

Import './app.styl' import './app.styl' // SRC /app.styl, equivalent to the file address above (absolute path) import '@/app.styl'Copy the code

2.6 Forbid map files from being generated by build projects

Map file, namely Javascript source map file, is to solve the confusion of compressed JS during debugging, can quickly locate the source code before compression auxiliary file. The release of this file exposes the source code. Therefore, you are advised to directly disable map file generation during build.

Config /webpack.config.js and change shouldUseSourceMap to false:

// Source maps are resource heavy and can cause out of memory issue for large source files. - // const shouldUseSourceMap = process.env.GENERATE_SOURCEMAP ! == 'false'; + const shouldUseSourceMap =false;Copy the code

3. Construction of project architecture

3.1 Project directory structure design

The project directory structure can be flexibly formulated according to the actual project. Here I share the common structure, mainly divided into public module directory, component module directory, page module directory and other parts, so that the project structure more clear and reasonable.

├ ─ / config < - webpack configuration directory ├ ─ / node_modules ├ ─ / public | ├ ─ the favicon. Ico < - page icon | └ ─ index. The HTML < - HTML page template ├ ─ / scripts < - node compiled script ├ ─ / SRC | ├ ─ / API < - API directory | | └ ─ index. The js < - API library | ├ ─ / common < - global public directory | | ├ ─ / fonts < - font file directory | | ├ ─ < - image files/images directory | | ├ ─ / js < - public js file directory | | └ ─ / style < - public style file directory | | | ├ ─ frame. The styl < -- All public style (import catalogue all other styl) | | | ├ ─ reset. Styl < - reset styles (if you use Ant Design, Do not need this file) | | | └ ─ global. Styl < - global public style | ├ ─ / components < - public module components directory | | ├ ─ / header < - head navigation module | | | ├ ─ index. The js < -- Header master file | | | └ ─ header. The styl < - the header style file | | └ ─... < - other modules | ├ ─ / pages < - page components directory | | ├ ─ / home < - home page directory | | | ├ ─ index. The js < - home master file | | | └ ─ home. Styl < | - home style file | ├ ─ / login < - login page directory | | | ├ ─ index. The js < - login master file | | | └ ─ the login. The styl < - login style file | | └ ─... < - other page | ├ ─ App. Js < - project main module | ├ ─ index. The js < - project entry documents | ├ ─. Gitignore | ├ ─ package. The json | ├ ─ README. Md | └ ─ yarn. The lockCopy the code

Next, follow the directory structure design above to start building the project.

3.2 Setting the Global Common Style

This tutorial uses Stylus as the CSS preprocessor language. You can choose Sass/Scss, Less or Stylus according to your own habits.

Introduce other common styles in frame.styl.

SRC/common/stylus/frame. Styl:

@import './reset.styl';
@import './global.styl';
Copy the code

SRC/common/stylus/global. Styl:

/* clear float */. Clearfix :after content: "." display: block height: 0 clear: both visibility: hidden .clearfix display:block .G-col-error color: #f81d22 .G-col-succ color: #0b8235Copy the code

SRC/common/stylus/reset. Styl:

After creating the file, leave the code blank. Because Ant Design is introduced later in this tutorial, you don’t need to set the reset style yourself.

Then add frame.styl to SRC /index.js:

Import React from 'React' import ReactDOM from 'React -dom' import App from './App' + // global style + import '@/common/stylus/frame.styl' ReactDOM.render(<App />, document.getElementById('root'))Copy the code

This allows you to use global styles directly on all pages.

3.3 Style naming conventions

According to my years of development experience, a reasonable style naming specification is of great help to project development, mainly reflected in the following aspects:

(1) Avoid contamination caused by duplicate style names.

(2) From the name can intuitively distinguish “component style”, “page style” (used to make custom adjustments to the component style on this page), “global style”.

(3) Fast positioning module, easy to find problems.

Share the style naming conventions for this tutorial:

G-xx: represents global styles, used to define common styles.

P-xx: represents the page style, which is used to set the background color, size, and customization of components on the page.

M-xx: represents the component style, focusing on the component style.

You can see in detail how the above specification is applied in subsequent tutorials.

4. Introduce Ant Design

Ant Design is an excellent UI library that is widely used in React projects. This share also specifically explains how to introduce Ant Design.

4.1 Installing Ant Design

Perform:

yarn add antd
Copy the code

4.2 Load on demand

Ant Design has many styles, but only a few of them may be used in a project, so there is no need to load them all. On-demand loading of styles can be implemented using babel-plugin-import.

Install Babel – plugin – import:

yarn add babel-plugin-import --dev
Copy the code

Modify the package. The json:

    "babel": {
        "presets": [
            "react-app"
M       ],
+       "plugins": [
+           [
+               "import",
+               {
+                   "libraryName": "antd",
+                   "style": "css"
+               }
+           ]
+       ]
    }
Copy the code

Then modify SRC/app.js to verify Antd:

import { Button } from 'antd'

function App() {
    return (
        <div className="App">
            <h1>React-App5</h1>
            <Button type="primary">Button</Button>
        </div>
    )
}

export default App
Copy the code

Run yarn start:

You can see that Antd’s Button component displays normally and Antd’s page reset style is in effect.

4.3 Setting Antd to Chinese

The default language of Antd is English. You need to set the following parameters to adjust Antd to Chinese.

Modify the SRC/index. Js:

import React from 'react' import ReactDOM from 'react-dom' + import { ConfigProvider } from 'antd' + import zhCN from 'antd/es/locale/zh_CN' import App from '. / App / / global style import '@ / common/stylus/frame. The styl' + const antdConfig = {+ locale: zhCN, + } M ReactDOM.render( + <ConfigProvider {... antdConfig}> + <App /> + </ConfigProvider>, + document.getElementById('root') + )Copy the code

4.4 Custom Antd Theme Color (optional)

If you do not need to customize Antd theme colors, you can skip this section.

Antd styles use Less as the development language. If you want to customize Ant Design theme colors, you need to have your project support Less (described in Section 2.3).

Modify the config/webpack. Config. Js:

if (preProcessor) { + let preProcessorOptions = { + sourceMap: True, +} + if (preProcessor === "less-loader") {+ preProcessorOptions = {+ sourceMap: true, + // Custom theme + lessOptions: {+ modifyVars: {+ // Customize global primary color, green + 'primary-color':' #67C23A', +}, + javascriptEnabled: true, + }, + } + } loaders.push( { loader: require.resolve('resolve-url-loader'), options: { sourceMap: isEnvProduction && shouldUseSourceMap, }, }, { loader: require.resolve(preProcessor), - // options: { - // sourceMap: true, - // }, + options: preProcessorOptions } ); }Copy the code

Modify package.json to change style from “CSS” to true:

    "babel": {
        "presets": [
            "react-app"
        ],
        "plugins": [
            [
                "import",
                {
                    "libraryName": "antd",
M                   "style": true
                }
            ]
        ]
    },
Copy the code

Restart the project and you can see that the button has changed to a custom theme color.

For more theme color configuration, please refer to Antd official website:

Ant. The design/docs/react /…

5 Page Development

This tutorial consists of two pages, login and home.

Changes to the project documents are as follows:

├ ─ / config ├ ─ / node_modules ├ ─ / public ├ ─ / scripts ├ ─ / SRC | ├ ─ / API | ├ ─ / common | ├ ─ / components | ├ ─ / pages | | ├ ─ M/home | | | ├ ─ index. The js M | | | └ ─ home. Styl | | ├ ─ / login M | | | ├ ─ index. The js M | | | ├ ─ the login. The styl + | | | └ ─ Logo. PNG | ├ ─ App. Js | ├ ─ index. The js | ├ ─. Gitignore | ├ ─ package. The json | ├ ─ README. Md | └ ─ yarn. The lockCopy the code

5.1 Building the Login Page

The page build code won’t go into detail, it’s pretty basic.

SRC/pages/login/index. Js:

import { Button, Input } from 'antd' import imgLogo from './logo.png' import './login.styl' function Login() { return ( <div <img SRC ={imgLogo} Alt ="" className="logo" /> <div className="ipt-con"> <Input placeholder=" account "/> </div> <div className="ipt-con"> < input. Password placeholder=" Password "/> </div> <div className="ipt-con"> <Button Type ="primary" block={true}> Login </Button> </div> </div>)} export default LoginCopy the code

SRC/pages/login/login. Styl:

.P-login
    position: absolute
    top: 0
    bottom: 0
    width: 100%
    background: #7adbcb
    .logo
        display: block
        margin: 50px auto 20px
    .ipt-con
        margin: 0 auto 20px
        width: 400px
        text-align: center
Copy the code

Temporarily modify the entry file code and replace the original App page with the login page to see the effect:

SRC/index. Js:

-   import App from './App'
+   import App from '@/pages/login'
Copy the code

5.2 Building the Home Page

Go straight to the code.

SRC/pages/home/index. Js:

import { Button } from 'antd' import './home.styl' function Home() { return ( <div className="P-home"> <h1>Home Page</h1> <div className="ipt-con"> <Button> </Button> </div>)} export default HomeCopy the code

SRC/pages/home/home. Styl:

.P-home
    position: absolute
    top: 0
    bottom: 0
    width: 100%
    background: linear-gradient(#f48c8d,#f4c58d)
    h1
        margin-top: 50px
        text-align: center
        color: #fff
    .ipt-con
        margin: 20px auto 0
        text-align: center
Copy the code

Temporarily modify the entry file code and change the initial page to the home page to see the effect:

SRC/index. Js:

-   import App from '@/pages/login'
+   import App from '@/pages/home'
Copy the code

5.3 Redirecting page routes

To redirect pages, install react-router-dom.

Perform:

yarn add react-router-dom
Copy the code

Now, make SRC/app.js the official routing configuration page and refactor the code.

SRC/App. Js:

import { HashRouter, Route, Routes, Navigate } from 'react-router-dom' import Login from '@/pages/login' import Home from '@/pages/home' function App() { Return (<HashRouter> <Routes> {/* Routes exactly match "/home", <Route exact path="/ Home "element={<Home />} /> { Element ={<Login />} /> {/* Not matched. The Login page is displayed. */} <Route path="*" Element ={<Navigate to="/ Login "/ >} /> </Routes> </HashRouter>)} export default AppCopy the code

Next, change the entry file back to the App routing page.

SRC/index. Js:

-   import App from '@/pages/home'
+   import App from './App'
Copy the code

Run yarn start to start the project and enter the route address. The page can be displayed normally.

Login page: http://localhost:3000/#/login

Home page: http://localhost:3000/#/home

5.4 Redirecting Pages on the React Component

Click the “Login” button on the Login page to jump to the Home page.

Modify the SRC/pages/login/index. Js:

+ import { useNavigate } from 'react-router-dom' import { Button, Input} from 'antd' import imgLogo from './logo.png' import './login.styl' function login () {+ // create route hook + const navigate = useNavigate() return ( ... <div className="ipt-con"> M <Button type="primary" block={true} onClick={()=>{navigate('/home')}}> . (abbreviated)Copy the code

In the same way, click the “Back to Login” button on the home page to jump to the login page.

Modify the SRC/pages/home/index. Js:

+ import { useNavigate } from 'react-router-dom' import { Button } from 'antd' import './home.styl' function Home() { + Const navigate = useNavigate() return (<div className=" p-home "> <h1> home Page</h1> <div ClassName ="ipt-con"> M <Button onClick={()=>{navigate('/login')}}> export default Home </Button> </div> </div>)Copy the code

It is now possible to click a button to redirect to a page.

5.5 Redirecting Pages on Non-React Components

In real projects, it is often necessary to jump to a page in a non-React component. For example, if the login authentication is invalid during AN API request, the login page is directly redirected. When an API request fails, a unified error message is displayed.

The unified treatment of these cases is, of course, the most appropriate to encapsulate into a common module. However, most of these purely functional modules are not React components, i.e. pure native JS. So you can’t use useNavigate().

The following describes how to redirect a page in a non-React component.

Additional history dependencies need to be installed. As of this writing, the latest version of History is 5.2.0, but history.push() only changes the address of the page’s address bar without actually jumping. There is also a lot of feedback on GitHub about the latest version of the bug. The current workaround is to install version 4.10.1.

Perform:

Yarn add [email protected]Copy the code

While reading this article, I recommend checking to see if a more recent version of History has been released (>5.2.0), installing the latest version of History and trying it out. If the bug persists, then installing 4.10.1.

After installation, create a new directory and file SRC/API /index.js:

import { createHashHistory } from 'history'

let history = createHashHistory()

export const goto = (path) => {
    history.push(path)
}
Copy the code

SRC /pages/home/index.js;

import { useNavigate } from 'react-router-dom' import { Button } from 'antd' + import { goto } from '@/api' import Styl 'function home () {const navigate = useNavigate() return (<div className=" p-home "> <h1> home Page < / h1 > + < div className = "ipt - con" > + < Button onClick = {() = > {goto ('/login ')}} > components outside jump < / Button > + < / div > < div ClassName ="ipt-con"> <Button onClick={()=>{navigate('/login')}}> export default Home </Button> </div> </div>)Copy the code

Click the “Component Hop out” button on the home page to jump to the login page. The actual jump code is in SRC/API /index.js (non-React components), which is ideal for encapsulating unified processing logic.

Subsequent sections will show how to encapsulate the API interface and implement a unified jump when an API call fails by means of component routing.

6 Component Development

This chapter is also very easy, and those of you who have been exposed to VUE should be well aware that, for the sake of completeness of the tutorial, it is best to keep it brief. Let’s simply implement a common header component.

6.1 Creating Header Components

The directory structure changes as follows:

| ├ ─ / components < - public module components + | | ├ ─ / header < - public header component + | | | ├ ─ index. The js + | | | └ ─ header. The stylCopy the code

SRC/components/headers/index. The js code:

import './header.styl'

function Header() {
    return <div className="M-header">Header</div>
}

export default Header
Copy the code

SRC/components/headers/header. Styl:

.M-header
    height: 40px
    line-height: 40px
    font-size: 36px
    color: #fff
    background: #409EFF
Copy the code

6.2 Importing the Header Component

Introduce the Header component in the login page.

SRC/pages/login/index. Js:

import { useNavigate } from 'react-router-dom' import { Button, Input } from 'antd' import imgLogo from './logo.png' + import Header from '@/components/header' import './login.styl' Const navigate = useNavigate() return (<div className=" p-login "> M <Header /> <img SRC ={imgLogo} Alt ="" className="logo" /> <div className="ipt-con"> <Input placeholder=" account "/> </div>... (abbreviated)Copy the code

Introduce the Header component in the home page in the same way.

SRC/pages/home/index. Js:

import { useNavigate } from 'react-router-dom' import { Button } from 'antd' + import Header from '@/components/header' Import {goto} from '@/ API 'import './home.styl' function home () {const navigate = useNavigate() return () <div className="P-home"> + <Header /> <h1>Home Page</h1>Copy the code

Run the project and the Header component has been added successfully.

6.3 Component Parameter Transfer

Those of you who have used VUE know that the VUE component has data and props.

Data is the data within the component;

Props is used to receive data from the parent component.

In React, if you use a Class defined component:

State is the data within the component;

Props is used to receive data from the parent component.

If you are using a function defined component (also known as a stateless component) :

Use useState() to manage the data within the component (hook);

Use props to receive data from the parent component.

Class components have explicit declaration cycle management, but the code is relatively less elegant than stateless components.

Stateless components manage the declaration cycle more efficiently by hook. Therefore, this tutorial uses stateless components throughout.

Here is a simple demonstration of how to implement passing data to child components.

Different values are passed to and displayed in the Header component via login and home, respectively.

Modify the SRC/pages/login/index. Js:

. (a) M < Header title = "login" info = {() = > {the console. The log (' info: login ')}} / >... (abbreviated)Copy the code

Modify the SRC/pages/home/index. Js:

. (a) M < Header title = "home" info = {() = > {the console. The log (' info: home ')}} / >... (abbreviated)Copy the code

Modify the SRC/components/headers/index. Js:

Import './header.styl's function header (props) {const {title, info} = props + // If info is present, Run info() + info && info() M return <div className=" m-header "> header :{title}</div>} export default headerCopy the code

Run to see if it has taken effect.

7 React Developer Tools Browser plug-in

To facilitate the react project debugging, you are advised to install the Chrome plug-in.

Go to the Chrome app store and search for React Developer Tools.

Once installed, open Chrome DevTools and click the Components button to see the React project code structure and various parameters.

Redux and related plug-ins

What is Redux used for? In plain English, Redux is used to manage global variables at the project level, and it can listen for changes in variables and alter the DOM in real time. When multiple modules need to display the same data dynamically, and those modules belong to different parent components or are on different pages, it can be cumbersome to implement and painful to track problems without Redux. So Redux solves this problem.

For those of you who have done VUE development, vuex is known as Redux, the react-Redux tool is also known as immutable.

Redux covers a lot of ground, and reading all the official documentation for each of the dependent components is not easy to digest. The share through a simple Demo, the story, the react – story, the story – thunk, immutable skewer the use of these depend on the component method, is very beneficial to understand.

8.1 installation redux

Perform:

yarn add redux
Copy the code

Installing redux alone is also possible, but more cumbersome. In Redux, you need to manually subscribe to update data in the store, but I won’t go into that here. Another plug-in (React-Redux) can be used to improve development efficiency.

8.2 install the react – story

Perform:

yarn add react-redux
Copy the code

React-redux allows data in a store to be mapped to the props of a component via the connect method, eliminating store subscriptions. Properties read from store in state are read by props instead.

Since Store (Section 8.5) is not covered, the use of React-Redux is covered in Section 8.8.

8.3 installation redux – thunk

Perform:

yarn add redux-thunk
Copy the code

Redux-thunk allows function-type data to be passed in actionCreators. This allows you to centralize business logic (such as interface requests) in actionCreator. Js, making it easy to reuse and simplifying the main file of the component.

8.4 Installing the Redux Browser Plug-in

To facilitate redux status tracking, you are advised to install the Chrome plug-in. This plug-in records every redux change, making it very easy to track patterns.

Do a scientific search for “Redux DevTools” in the Chrome app store and install it.

It cannot be used directly after installation and needs to be configured in the project code. Here’s the explanation.

8.5 create the store

After installing the various plug-ins above, store can be used to manage state data.

If the project is simple, with only one or two pages, you can create just one master store to manage the overall project. The directory structure is as follows:

├ ─ / SRC + | ├ ─ / store + | | ├ ─ actionCreators. Js + | | ├ ─ the js < -- defining methods of constant + | | ├ ─ index. The js + | | └ ─ reducer. JsCopy the code

Here is the code for each file:

SRC/store/index. Js:

import { createStore, applyMiddleware, Compose} from 'redux' import reducer from './reducer' import thunk from 'redux-thunk const composeEnhancers = typeof window === 'object' && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) : compose const enhancer = composeEnhancers( applyMiddleware(thunk) ); const store = createStore( reducer, enhancer ) export default storeCopy the code

This is the core code of the Store, which supports Redux DevTools. At the same time, redux-Thunk was integrated with Redux’s applyMiddleware functionality to create the Store.

SRC/store/the js:

export const SET_DATA = 'SET_DATA'
Copy the code

This constant definition file was created so that it can be referenced by both reducer.js and actionactionine.js below for unified modification and management.

SRC/store/actionCreators. Js:

import * as constants from './constants'

export const getData = (data) => ({
  type: constans.SET_DATA,
  data
})
Copy the code

SRC/store/reducer. Js:

Import * as constants from './constants' // initial defaultState const defaultState = {myData: Null} const reducer = (state = defaultState, action) => {// State is a reference type and cannot be directly modified, otherwise state changes cannot be monitored. So you need to make a copy of it and change it, and then return the new state. let newState = Object.assign({}, state) switch(action.type) { case constants.SET_DATA: newState.myData = action.data return newState default: return state } } export default reducerCopy the code

The code above sets up a myData in the store. At present, state is still a bit troublesome to modify. How to better solve this problem will be discussed in section 8.8.

At this point, you probably still don’t know how to use Redux. Real projects rarely have just one master store library to manage. Therefore, the use of Redux is described in detail in the library section below.

8.6 Store decomposition of complex projects

When the project has many pages, the maintenance cost will be higher if the data is centralized in one store. Let’s share how to break the Store into its components.

In general, each component has its own store, with SRC/Store as the aggregate that integrates each component’s store.

The header and login components are used as examples to create their own stores. The file structure is the same as the store collection.

The directory structure changes as follows:

| | ├ ─ / components | | | └ ─ / header + | | | ├ ─ / store + | | | | ├ ─ actionCreators. Js + | | | | ├ ─ the js + | | | | ├ ─ index. Js + | | | | └ ─ reducer. Js | | | ├ ─ header. The styl | | | └ ─ index. The js | | ├ ─ / pages | | | ├ ─ / login + | | | | ├ ─ / store + | | | | | ├ ─ actionCreators. Js + | | | | | ├ ─ the js + | | | | | ├ ─ index. The js + | | | | | └ ─ reducer. Js | | | | ├ ─ login. Styl | | | | └ ─ index. JsCopy the code

SRC/components/headers/store/index, js and SRC/pages/login/store/index, js:

import reducer from './reducer'
import * as actionCreators from './actionCreators'
import * as constants from './constants'

export { reducer, actionCreators, constants}
Copy the code

This means that other files in the current component store are pooled together as a unified outlet.

SRC/components/headers/store/the js:

const ZONE = 'components/header/'

export const SET_DATA = ZONE + 'SET_DATA'
Copy the code

ZONE is used to avoid duplication with the constants of other components.

In the same way, create a store under Login.

SRC/pages/login/store/the js:

const ZONE = 'pages/login/'

export const SET_DATA = ZONE + 'SET_DATA'
Copy the code

SRC/components/headers/store/actionCreators. Js and SRC/pages/login/store/actionCreators js:

import * as constants from './constants'

export const setData = (data) => ({
  type: constants.SET_DATA,
  data
})
Copy the code

SRC/components/headers/store/reducer, js:

Import * as constants from './constants' // initial defaultState const defaultState = {myHeaderData: Null} const reducer = (state = defaultState, action) => {// State is a reference type and cannot be directly modified, otherwise state changes cannot be monitored. So you need to make a copy of it and change it, and then return the new state. let newState = Object.assign({}, state) switch(action.type) { case constants.SET_DATA: newState.myHeaderData = action.data return newState default: return state } } export default reducerCopy the code

In the same way, the SRC/pages/login/store/reducer. Js:

Import * as constants from './constants' // initial defaultState const defaultState = {myLoginData: Null} const reducer = (state = defaultState, action) => {// State is a reference type and cannot be directly modified, otherwise state changes cannot be monitored. So you need to make a copy of it and change it, and then return the new state. let newState = Object.assign({}, state) switch(action.type) { case constants.SET_DATA: newState.myLoginData = action.data return newState default: return state } } export default reducerCopy the code

Then modify the project Store set and change the directory structure as follows:

├ ─ / SRC | ├ ─ / store - | | ├ ─ actionCreators. Js < delete - - | | ├ ─ the js < - delete | | ├ ─ index. The js M | | └ ─ reducer. JsCopy the code

SRC /store/reducer.js rewrite as follows:

import { combineReducers } from 'redux'
import { reducer as loginReducer } from '@/pages/login/store'
import { reducer as headerReducer } from '@/components/header/store'

const reducer = combineReducers({
    login: loginReducer,
    header: headerReducer
})

export default reducer
Copy the code

The above code is used to import the login and header stores, then merge them together via combineReducers, and add a unique object key value to each.

The benefits are clear:

  1. Avoid store data contamination of each component.
  2. Components maintain their own stores independently, reducing maintenance costs.

This approach to store maintenance is highly recommended.

8.7 Using IMmutable For Installation

In section 8.5, it is mentioned that state cannot be directly modified in store, because state is a reference type and direct modification may result in undetected data changes.

A. immutable B. immutable C. immutable D. immutable Data created using IMMUTABLE data is immutable, and any modification to immutable data will return a new IMmutable data, without changing the original IMmutable data.

Immutable. Js provides many methods that make it easy to modify reference data of object or array types.

To install immutable and redux-immutable, run:

yarn add immutable redux-immutable
Copy the code

Then modify the code:

SRC/store/reducer. Js:

- import { combineReducers } from 'redux' + import { combineReducers } from 'redux-immutable' ... (abbreviated)Copy the code

CombineReducers is redux-immutable.

And then modify the SRC/pages/login/store/reducer. Js:

Import * as constants from './constants' + import {fromJS} from 'immutable' // defaultState M const defaultState = fromJS({ myLoginData: null, M }) + const getData = (state, action) => { + return state.set('myLoginData', Action. Data) +} const reducer = (state = defaultState, action) => {// State is a reference type and cannot be directly modified. Otherwise, state changes cannot be measured. So you need to make a copy of it and change it, and then return the new state. - // let newState = Object.assign({}, state) switch (action.type) { case constants.SET_DATA: - // newState.myLoginData = action.data - // return newState return getData(state, action) default: return state } } export default reducerCopy the code

The immutable intervention is to convert primitive JS types to immutable types using the fromJS method.

Since state is already immutable, you can use the IMmutable set method to modify the data and return a new state. The code is much simpler, do not need to manually through Object. Assign and other methods to copy and reprocess.

Code changes to the header component will not be described again.

There are many other ways to use immutable, as described in the official documentation:

immutable-js.com/docs/v4.0.0

8.8 Interconnecting react- Redux with Store

React-redux and Store can be used by all components.

Modify the SRC/index. Js:

import React from 'react' import ReactDOM from 'react-dom' import { ConfigProvider } from 'antd' import zhCN from 'antd/es/locale/zh_CN' import App from './App' + import { Provider } from 'react-redux' + import store from './store' // Global style import '@ / common/stylus/frame. The styl' const antdConfig = {locale: zhCN,} ReactDOM. Render (< ConfigProvider {... antdConfig}> + <Provider store={store}> <App /> + </Provider> </ConfigProvider>, document.getElementById('root') )Copy the code

This code uses the React-Redux Provider to deliver the store to the entire App.

In components that need to use store, the connect method provided by React-Redux is used to wrap the component.

8.9 Set and read the Redux variable in real time on the Login page

Using login as an example, modify SRC /pages/login/index.js:

import { useNavigate } from 'react-router-dom' import { Button, Input } from 'antd' import imgLogo from './logo.png' import Header from '@/components/header' + import { connect } from 'react-redux' + import * as actionCreators from './store/actionCreators' import './login.styl' M function Login(props) {  + const { myLoginData, SetData} = props // create route hooks const navigate = useNavigate() return (<div className=" p-login "> <Header title="login") info={() => { console.log('info:login') }} /> <img src={imgLogo} alt="" className="logo" /> + <div className="ipt-con">login store: MyData ={myLoginData}</div> + <div className="ipt-con"> + <button onClick={() => {setData('123456')}}> Change login </button> + </div> <div className="ipt-con"> <Input placeholder=" account "/> </div> <div className="ipt-con"> < input. Password placeholder=" Password "/> </div> <div className="ipt-con"> <Button type="primary" block={true} onClick={() => </Button> </div> </div>)} + // To map data in store to components props + const mapStateToProps = (state) =>{ + return {+ // The first element of the array login, which corresponds to the login branch name defined in SRC /store/reducer.js + myLoginData: state.getIn(['login', 'myLoginData']), // Map store Dispatch to component props + const mapDispatchToProps = (Dispatch) => ({+ setData(data) {+ const action  = actionCreators.setData(data) + dispatch(action) + }, + }) - // export default Login M export default connect(mapStateToProps, mapDispatchToProps)(Login)Copy the code

Key points:

  1. Notice on the last line of the code that the export data is wrapped by the connect method.

  2. MapStateToProps and mapDispatchToProps map both state and Dispatch in store to component props. This can be accessed directly through props, where changes in the data in the store directly change props to trigger a view update for the component.

  3. The state.getin () method is derived from redux-immutable.

After clicking the button, you can see that the myData displayed on the page has changed, which can be visually tracked through Redux DevTools.

8.10 Read the Redux variable in real time from the Header component

Next, you implement a real-time reading of myLoginData set on the Login page in the Header component.

Modify the SRC/components/headers/index. Js:

+ import {connect} from 'react-redux' import './header.styl' function header (props) {M const { Title, info, myLoginData} = props Info () info && info() return (<div className=" m-header "> header :{title} + <span style={{marginLeft: MyLoginData :{myLoginData}</span> </div>)} + // Map data in store to props + const mapStateToProps = (state) => {+ Return {+ // The first element of the array login, which corresponds to the name of the login branch library defined in SRC /store/reducer.js + myLoginData: state.getIn(['login', 'myLoginData']), + } + } - // export default Header M export default connect(mapStateToProps, null)(Header)Copy the code

Since only myLoginData, which reads Redux, is used in the header, the mapDispatchToProps method is not needed.

This is done in real time via Redux, rather than via parent-child component passing. So the same approach can be used directly in other pages or components, regardless of the component’s parent-child relationship.

Now click “Change myData for Login Store” and you can see that the Header component can get myLoginData in real time.

In section 8.6, the store of the header component is also set to myHeaderData, which is not used in the actual demo. It is only used to demonstrate store branch management.

8.11 Redux development summary

There’s a lot of Redux stuff going on, but I can’t explain why I use these dependency packages. Here do a summary, easy to digest understanding.

React-redux, Redux-thunk, and immutable are all developed around simplifying Redux.

React-redux is intended to simplify the tedious process of redux changing state by subscription.

Redux-thunk is designed to enable redux dispatches to support function-type data. Review the mapDispatchToProps for the login page code in section 8.9.

Immutable is a way to solve the problem that data in a store cannot be modified by direct assignment (changes in reference type data make it impossible to detect changes in data).

For further study, please refer to the official documentation:

Story:

Redux.js.org/introductio…

The react – story:

www.redux.org.cn/docs/react-…

Story – thunk:

Redux.js.org/usage/writi…

Immutable:

immutable-js.com/docs/v4.0.0

9 Encapsulates common API libraries based on AXIOS

In order to facilitate the maintenance of THE API, it is a good solution to centralize the management of each API address and related methods.

9.1 installation axios

Axios is a very popular API request tool, so install it first.

Perform:

yarn add axios
Copy the code

9.2 Encapsulating public API Libraries

Go straight to the code.

SRC/API/index. Js:

import axios from 'axios' import { createHashHistory } from 'history' import { Modal } from 'antd' let history = CreateHashHistory () createHashHistory() Export const goto = (path) => {history.push(path)} // Development environment address let API_DOMAIN = '/ API /' if (process.env.node_env === 'production') {// Official environment address API_DOMAIN = 'http://xxxxx/api/'} // User login information name stored in localStorage export const SESSION_LOGIN_INFO = 'loginInfo'; Export const API_CODE = {// API request normal OK: 200, // API request normal, data exception ERR_DATA: 403, // API request normal, empty data ERR_NO_DATA: 301, // API request normal, login exception ERR_LOGOUT: 401,} // Export const API_FAILED = 'Network connection is abnormal, please try again later' export const API_LOGOUT = 'Your account has been logged in to another device, Please re-log in 'export const apiReqs = {// Login (save login information to localStorage after success) signIn: (config) => { axios .post(API_DOMAIN + 'login', config.data) .then((res) => { let result = res.data config.done && config.done(result) if (result.code === API_CODE.OK) { window.localStorage.setItem( SESSION_LOGIN_INFO, JSON.stringify({ uid: result.data.loginUid, nickname: result.data.nickname, token: result.data.token, }) ) config.success && config.success(result) } else { config.fail && config.fail(result) } }) .catch(() => { config.done && config.done() config.fail && config.fail({ message: API_FAILED,})}, // log out (log out, delete login information from localStorage) () => { const { uid, token } = getLocalLoginInfo() let headers = { loginUid: uid, 'access-token': token, } let axiosConfig = { method: 'post', url: API_DOMAIN + 'logout', headers, } axios(axiosConfig) .then((res) => { logout() }) .catch(() => { logout() }) }, // getUserList getUserList: (config) => { config.method = 'get' config.url = API_DOMAIN + 'user/getUserList' apiRequest(config) }, // modifyUser information modifyUser: (config) => { config.url = API_DOMAIN + 'user/modify' apiRequest(config) }, } export function getLocalLoginInfo() {return Json.parse (window.localstorage [SESSION_LOGIN_INFO])} export function logout() { Window. The localStorage. RemoveItem (SESSION_LOGIN_INFO) history. Push ('/login ')} / * * API request packaging (with authentication information) * config in history: Method: [required] Request method * config.url: [required] Request URL * config.data: Request data * config.formData: Whether to submit in formData format (for uploading files) * config.success(res): callback for success * config.fail(err): callback for failure * config.done(): */ export function apiRequest(config) {const loginInfo = JSON.parse(window.localStorage.getItem(SESSION_LOGIN_INFO)) if (config.data === undefined) { config.data = {} } Config) method = config) method | | 'post' / / encapsulated header information let headers = {loginUid: loginInfo? loginInfo.uid : null, 'access-token': loginInfo ? loginInfo.token : null, If (config.formData) {headers[' content-type '] = 'multipart/form-data' data = new FormData() Object.keys(config.data).forEach(function (key) { data.append(key, Config. Data [key])})} else {data = config.data} let axiosConfig = {method: config.method, url: Config. Url, headers,} If (config.method === 'get') {axiosconfig. params = data} else {axiosconfig. data = data} // start the request axios(axiosConfig) .then((res) => { let result = res.data config.done && config.done() if (result.code === Api_code.err_logout) {// If the login information is invalid, Modal. Error ({title: result.message, // After clicking OK, the login interface onOk is directly jumped: () => {logout()},})} else {// If login information is normal, Success && config.success(result)}}). Catch ((err) => {// If the interface fails or an error occurs, The Modal dialog box for Antd is modal. error({title: API_FAILED,}) // Execute the fail callback config.fail && config.fail() // execute the done callback config.done && config.done()})}Copy the code

More code, the necessary remarks have been written, no longer repeat.

The following aspects are mainly achieved here:

  1. All apis of the project are managed uniformly through apiReqs.

  2. Through the apiRequest method, the unified business logic such as token authentication, login status failure and request error is realized.

Why don’t the signIn and signOut methods call the apiRequest as do getUserList and modifyUser?

Because the logic of signIn and signOut is special, signIn does not read localStorage, and signOut needs to clear localStorage, these two logic is different from other APIS, so it is implemented separately.

9.3 Mock.js Installation and Use

During development, mock. js is often used to intercept Ajax requests and return preconfigured data to make it easier for the front-end to debug the interface alone. This section shows you how to use mock. js in your React project.

Perform installation:

yarn add mockjs
Copy the code

Create a new mock.js under SRC as follows:

Import Mock from 'mockjs' const domain = '/ API /' // Function () {let result = {code: 200, message: 'OK', data: {loginUid: 10000, nickname: 'Mr. Rabbit ', token: 'yyds2022' } } return result })Copy the code

Then introduce mock.js in SRC /index.js:

import React from 'react' import ReactDOM from 'react-dom' import { ConfigProvider } from 'antd' import zhCN from 'antd/es/locale/zh_CN' import App from './App' import { Provider } from 'react-redux' import store from './store' + import './mock' ... (abbreviated)Copy the code

It’s so simple. This way, any request for/API /login in your project will be intercepted by mock.js and will return the simulated data from mock.js.

9.4 Making an API Request

Continue refining the login page to implement an API request.

SRC/pages/login/index. Js:

+ import { useState } from 'react' import { useNavigate } from 'react-router-dom' import { Button, Input } from 'antd' import imgLogo from './logo.png' import Header from '@/components/header' import { connect } from 'react-redux' import * as actionCreators from './store/actionCreators' + import { apiReqs } from '@/api' import './login.styl' function Login(props) { const { myLoginData, SetData} = props // create route hooks const navigate = useNavigate + // Self-maintained real-time data in the component + const [account, setAccount] = useState('') + const [password, SetPassword] = useState(") // Login + const login = () => {+ apireqs.signin ({+ data: { + account, + password + }, + success: (res) => { + console.log(res) + navigate('/home') + } + }) + } return ( <div className="P-login"> ... <div className="ipt-con"> M <Input placeholder=" account "value={account} onChange={(e)=>{setAccount(e.target.value)}} /> </div> <div className="ipt-con"> M < input. Password placeholder=" Password "value={Password} onChange={(e)=>{setPassword(e.target.value)}} /> </div> <div className="ipt-con"> M <Button type="primary" block={true} OnClick ={login}> </Button> </div>... (abbreviated)Copy the code

On the Login page, click Login. The login page is displayed.

You can see the mock request data returned from MockJS on the Console.

However, there are no initiated requests in Network Fetch/XHR. Because the request was intercepted by MockJS, no real request was actually made.

9.5 Setting reverse proxy Requests in the Development Environment

In the React development environment, initiating real situations often leads to cross-domain problems. For example, by default, the demo project will run at http://localhost:3000 after yarn start is executed. An API backend service is started locally at http://localhost. Requests can have cross-domain problems due to inconsistent ports. Reverse proxies can be implemented with the HTTP-proxy-middleware tool.

Perform installation:

yarn add http-proxy-middleware --dev
Copy the code

Create setupproxy.js under SRC with the following code:

Const {createProxyMiddleware} = require('http-proxy-middleware'); / / const {createProxyMiddleware} = require('http-proxy-middleware'); Module. exports = function (app) {app.use('^/ API ', createProxyMiddleware({// exports = function (app) {app.use('^/ API ', createProxyMiddleware( 'http://localhost', changeOrigin: true }) ) }Copy the code

As long as the request address begins with “/ API “, then reverse proxy to http://localhost domain, cross domain problem solved! You can modify it according to actual requirements.

Be sure to comment out mockJS, otherwise it will be intercepted.

Modify the SRC/index. Js:

-   import './mock'
Copy the code

※ Note: After setting setupproxy. js, you must restart the project to take effect.

Console allows you to see the actual data returned by the back-end API, and Network allows you to see the requested data.

10 the build project

One more step is required before building. Otherwise, the file references in the build version page are absolute paths and the page will be blank after running.

Modify the package. The json:

"Name" : "the react - app5", "version" : "0.1.0 from", "private" : true, + "homepage" : ". / ",... (abbreviated)Copy the code

Perform:

yarn build
Copy the code

The generated files are in the build directory of the project root directory. Open index.html and you can see the working project.

11 Project Git source code

This project has been uploaded to Gitee and GitHub for easy download.

Gitee:

Gitee.com/betaq/react…

GitHub:

Github.com/Yuezi32/rea…

conclusion

That’s it for this React Bucket tutorial. It’s a long, complete tutorial that took a long time to put together, validate, and weigh every sentence. I hope it helps. For more wonderful and detailed development tutorials, please read my wechat public number “Sleeping plum and smelling flowers”.

React: Build a React project from Scratch

Read more

React+Antd 插件 图 片

React+Antd 插件 图 片

Electronic 17+React17+Antd Architecture Project