The front-end technology is developing too fast, and some libraries are being upgraded all the time. Now online construction tutorials are either incomplete or the version is too low. I have compiled some tutorials and my own understanding to make it easier for people to quickly start with the React framework. This tutorial is aimed at beginners and technology stack transitioners. Note :(this tutorial was written on 2019-3-29, please note the version)!!
When you read, if you find any problems, please put forward, I will update in time (I am lazy, some commands did not hit out, please read carefully to avoid omission!!)
preface
I was also a member of the React team on the way in. Since I joined the react team on the way in, I had little understanding of the overall framework, and I always had a half-understanding of the existing DVA framework. When I encounter problems, I always need to find information to solve them, and some problems are difficult to solve in the end. In order to facilitate my own understanding of the whole React related technology, and avoid people like me to continue to step on the hole, I can follow this tutorial to have a comprehensive understanding. (master do not take)!!
Project introduction
1. The technology stack is currently up to date
- The node 8.11.1
- The react 16.8.6
- The react – the router – dom 5.0.0
- Redux 4.0.1
- Webpack 4.28.2
2. Package management tool
NPM yarn is commonly used. In this case, I use YARN. If I use NPM, pay attention to the difference between commands
Just start
Initialize the project
- Create a directory and enter
mkdir react-cli && cd react-cli
Copy the code
- Initialize the project and fill in the project information (press Enter)
npm init
Copy the code
Install webpack
yarn global add webpack -D
yarn global add webpack-cli -D
Copy the code
- Yarn uses add to add packages. -d equals –save-dev -s equals –save
- The difference between -d and -s: -d is what you rely on during development, –S is what you rely on after release
- -g is a global installation, so we can use the webpack command later.
After installation, create a new build directory and place the webPack-based development configuration webpack.dev.config.js
mkdir build && cd build && echo. > webpack.dev.config.js
Copy the code
The configuration is simple: configure the entry and output
const path = require('path'); Module.exports = {/* exports */ entry: path.join(__dirname,'.. /src/index.js'/ / output: {path: path.join(__dirname,'.. /dist'),
filename: 'bundle.js'}};Copy the code
Then according to the address of the entry file we configured, create.. / SRC /index.js (please note that the SRC directory is the same as the build directory)
mkdir src && cd src && echo. > index.js
Copy the code
Then write a line
document.getElementById('app').innerHTML = "Hello React";
Copy the code
Now execute the Webpack command in the root directory
webpack --config ./build/webpack.dev.config.js
Copy the code
We can see that the dist directory and bundle.js are generated. Next, we’ll create a new index.html file in the dist directory to reference the packaged file
<! doctype html> <html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<div id="app"></div>
<script type="text/javascript" src="./bundle.js" charset="utf-8"></script>
</body>
</html>
Copy the code
Then double-click to open index.html and we’ll see the browser output
Hello React
Copy the code
So we have a basic packaging function done!!
mode
Webpack4 requires us to specify the mode type to distinguish between the development environment and the production environment. It will automatically implement the corresponding function for us. Mode can be written to the startup command –mode=production or development, We can also write to the configuration file, where we add the mode property to webpack.dev.config.js.
/* entry */ entry: path.join(__dirname,'.. /src/index.js'),
mode:'development'.Copy the code
The warning disappears when the pack command is executed.
babel
Babel takes JavaScript code written in the latest standards and compiles it down to a version that can be used anywhere today. This process is called source-to-source compilation, also known as transformation compilation. (The Babel version used in this tutorial is 7, please note that the package name and configuration are different from 6.)Copy the code
- @babel/core calls Babel’s API for transcoding
- @babel/preset-env is used to parse ES6
- @babel/preset- React is used to parse JSX
- Babel – loader loader
yarn add @babel/core @babel/preset-env @babel/preset-react babel-loader -D
Copy the code
Then create a new Babel configuration file in the root directory
babel.config.js
const babelConfig = {
presets: ["@babel/preset-react"."@babel/preset-env"],
plugins: []
}
module.exports = babelConfig;
Copy the code
Change webpack.dev.config.js to add babel-loader!
/*cacheDirectory is used to cache compilation results, and the next compilation speed */ module: {rules: [{test: /\.js$/,
use: ['babel-loader? cacheDirectory=true'],
include: path.join(__dirname, '.. /src')}}]Copy the code
Now let’s test if we can escape ES6~ correctly
Modify the SRC/index. Js
*/ var func = STR => {document.getelementbyid ('app').innerHTML = str;
};
func('I'm using Babel now! ');
Copy the code
Then run the package command
webpack --config ./build/webpack.dev.config.js
Copy the code
Now refresh the index.html below dist to see the browser output
I'm using Babel now!Copy the code
If you’re interested, open the bundle.js package, and at the bottom you’ll find the ES6 arrow functions converted to normal function functions
react
Here’s our focus: Connect to React
yarn add react react-dom -S
Copy the code
Note: -s is used here to ensure the dependency of the production environment
Modify SRC /index.js to use react
import React from 'react';
import ReactDom from 'react-dom'; ReactDom.render( <div>Hello React! </div>, document.getElementById('app'));
Copy the code
Execute pack command
webpack --config ./build/webpack.dev.config.js
Copy the code
Refresh index.html to see the effect.
SRC SRC components directory, create a Hello directory, create an index.js directory, and say:
import React, { PureComponent } from 'react';
export default class Hello extends PureComponent {
render() {
return<div> Hello, componentized -React! </div> ) } }Copy the code
Then let’s modify SRC /index.js to reference the Hello component!
import React from 'react';
import ReactDom from 'react-dom';
import Hello from './components/Hello';
ReactDom.render(
<Hello/>, document.getElementById('app'));
Copy the code
‘./components/Hello’./components/Hello’
Execute the package command in the root directory
webpack --config ./build/webpack.dev.config.js
Copy the code
Open index.html to see what it looks like
Order to optimize
Each time you package, you enter a long package command, which is very troublesome, so let’s optimize this.
Modify the script object in package.json, add the build property, and write our package command.
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"."build": "webpack --config ./build/webpack.dev.config.js"
},
Copy the code
Now we just need to execute NPM run build! (Except for start, which is a built-in command, all new commands need to be run)
react-router
Now let’s connect the React route to the React router
yarn add react-router-dom -S
Copy the code
Next, in order to use routing, we create two pages for the content of the route switch. Create a pages directory under SRC. Then create a home directory under pages, and create a page directory inside index.js.
src/pages/home/index.js
Copy the code
import React, {PureComponent} from 'react';
export default class Home extends PureComponent {
render() {
return (
<div>
this is home~
</div>
)
}
}
Copy the code
src/pages/page/index.js
Copy the code
import React, {PureComponent} from 'react';
export default class Page extends PureComponent {
render() {
return (
<div>
this is Page~
</div>
)
}
}
Copy the code
Two pages are written, and then we create our menu navigation component
components/Nav/index.js
Copy the code
import React from 'react';
import { Link } from 'react-router-dom';
export default () => {
return (
<div>
<ul>
<li><Link to="/"</li> <li><Link to="/page">Page</Link></li>
</ul>
</div>
)
}
Copy the code
Note: Change the current route using the Link component
Then we create router.js under SRC, write our routes, and associate them with the page
import React from 'react';
import { Route, Switch } from 'react-router-dom'; // Import the page import Home from'./pages/home';
import Page from './pages/page'; // Route const getRouter = () => (<Switch> <Route exact path="/" component={Home}/>
<Route path="/page" component={Page}/>
</Switch>
);
export default getRouter;
Copy the code
Pages and menus and routes are written, and we associate them. In the SRC/index. In js
import React from 'react';
import ReactDom from 'react-dom';
import {BrowserRouter as Router} from 'react-router-dom';
import Nav from './components/Nav';
import getRouter from './router';
ReactDom.render(
<Router>
<Nav/>
{getRouter()}
</Router>,
document.getElementById('app'))Copy the code
Now you can see the content after executing the NPM Run build package, but clicking on the menu does not respond, which is normal. Since we are still using the local disk path and not the IP + port format, let’s introduce webpack-dev-server to start a simple server.
yarn global add webpack-dev-server -D
Copy the code
Modify webpack.dev.config.js to add webpack-dev-server configuration.
// webpack-dev-server
devServer: {
contentBase: path.join(__dirname, '.. /dist'),
compress: true// gzip compressed host:'0.0.0.0'// Allow IP access to hot:true, // Hot updatehistoryApiFallback:true404 port: 8000 // portCopy the code
Note: contentBase generally does not allow access to files in the specified directory. Here we use index.html below dist
Then create a new startup command in package.json
"start": "webpack-dev-server --config ./build/webpack.dev.config.js".Copy the code
Run the NPM start command and open http://localhost:8000 to see the content, and can switch routes!
The proxy agent
DevServer has a proxy property to set up our proxy
devServer: { ... Proxy: {// Configure the service proxy'/api': {
target: 'http://localhost:8000',
pathRewrite: {'^/api' : ' '}, // changeOrigin:true}, port: 8000 // port,Copy the code
If you have a back-end service on localhost:8000, you can enable the proxy in this way. Request to the/API/users will now be agent to request http://localhost:8000/users. (Note the second attribute here, which replaces ‘/ API ‘with ”). ChangeOrigin: True helps us with cross-domain issues.
Devtool optimization
When a startup error or something like an interruption occurs, you may find that the packaged code is nowhere to go. Let’s add it in webpack
devtool: 'inline-source-map'
Copy the code
Then you can see the code we wrote in srouce, and you can also interrupt debugging
File path optimization
When we refer to a component or page, we usually refer to a component or page. /. If the file level is too deep, it can cause.. /.. /.. / is difficult to maintain and read, for which Webpack provides an alias configuration.
See here: Remember that the name cannot be declared as the name of any other package you import. The alias will overwrite your package name, making it impossible to reference other packages. Chestnut: Redux, React, etc
First add it to webpack.dev.config.js
resolve: {
alias: {
pages: path.join(__dirname, '.. /src/pages'),
components: path.join(__dirname, '.. /src/components'),
router: path.join(__dirname, '.. /src/router')}}Copy the code
We can then import the component to router.js
// Import the page import Home from'./pages/home';
import Page from './pages/page'; // Import the page import Home from'pages/home';
import Page from 'pages/page';
Copy the code
The more complex the level of functionality, the better.
redux
The next step is to integrate Redux, so let’s skip the theory and use Redux for one of the most common examples, counters. First, we created a redux directory under SRC, in which we created two directories, Actions and reducers, respectively, to store our actions and reducer.
Import redux YARN add redux-sCopy the code
Under actions counter. Js
/*action*/
export const INCREMENT = "counter/INCREMENT";
export const DECREMENT = "counter/DECREMENT";
export const RESET = "counter/RESET";
export function increment() {
return {type: INCREMENT}
}
export function decrement() {
return {type: DECREMENT}
}
export function reset() {
return {type: RESET}
}
Copy the code
Directory under reducers under counter. Js
import {INCREMENT, DECREMENT, RESET} from '.. /actions/counter'; /* * initState */ const initState = {count: 0}; /* * reducer */export default function reducer(state = initState, action) {
switch (action.type) {
case INCREMENT:
return {
count: state.count + 1
};
case DECREMENT:
return {
count: state.count - 1
};
case RESET:
return {count: 0};
default:
return state
}
}
Copy the code
Add aliases for Actions and Reducers in the WebPack configuration.
actions: path.join(__dirname, '.. /src/redux/actions'),
reducers: path.join(__dirname, '.. /src/redux/reducers')
Copy the code
Actions create a reducer that returns an action class that has a type attribute that determines which reducer to execute. Reducer is a pure function (it only accepts and returns parameters without introducing other variables or doing other functions). Reducer mainly accepts old state and action, determines execution according to action type, and then returns a new state.
Special Note: You may have many reducer,typeIt must be globally unique and is usually implemented with a prefix. He owns counter in Counter /INCREMENTtypeThe prefix.Copy the code
Next we will create a store.js in the redux directory.
import {createStore} from 'redux';
import counter from 'reducers/counter';
let store = createStore(counter);
export default store;
Copy the code
Specific functions of Store:
- Maintain application state;
- Provide the getState() method to getState;
- Provide dispatch(Action) to trigger the reducers method to update state;
- Register a listener with subscribe(listener);
- Unsubscribe the listener via a function returned by subscribe(listener).
Next we create a Counter page to use the Redux data. Create a counter directory and index.js in the Pages directory. Our actions are referenced in the page to perform the Reducer change data.
import React, {PureComponent} from 'react';
import { connect } from 'react-redux';
import { increment, decrement, reset } from 'actions/counter';
class Counter extends PureComponent {
render() {
return<div> <div> <div onClick={() => this.props. Increment ()}> </div <div OnClick = {() = > this. Props. The decrement ()} > the decrement < / button > < button onClick = {() = > this. Props. The reset ()} > reset < / button > < / div >)}}export default connect((state) => state, dispatch => ({
increment: () => {
dispatch(increment())
},
decrement: () => {
dispatch(decrement())
},
reset: () => {
dispatch(reset())
}
}))(Counter);
Copy the code
What is connect? React-redux provides a method, connect. Connect has two parameters: mapStateToProps, which converts the state of redux to the Props of the component, and mapDispatchToprops, which converts the actions method to the Props property function.
Then we introduce react-redux:
yarn add react-redux -S
Copy the code
Next we add the counter menu and route to show our counter functionality.
Nav component <li><Link to="/counter">Counter</Link></li>
Copy the code
router.js
import Counter from 'pages/counter';
---
<Route path="/counter" component={Counter}/>
Copy the code
Finally, use the store functionality in SRC /index.js
import {Provider} from 'react-redux';
import store from './redux/store';
ReactDom.render(
<Provider store={store}>
<Router>
<Nav/>
{getRouter()}
</Router>
</Provider>,
document.getElementById('app'))Copy the code
The Provider component allows all components to access the Store. You don’t have to do it manually. You don’t have to listen in manually. So let’s start NPM start, and then we can see our counters in the browser.
There will be a lot of Reducer in our development. Redux provides a combineReducers function to merge reducer, which is very simple to use. Introduce combineReducers in store.js and use it.
import {combineReducers} from "redux";
let store = createStore(combineReducers({counter}));
Copy the code
Then in the Counter page component, change the state injected by CONNECT to counter (select the data set you want from the state complete tree).
export default connect(({counter}) => counter, dispatch => ({
increment: () => {
dispatch(increment())
},
decrement: () => {
dispatch(decrement())
},
reset: () => {
dispatch(reset())
}
}))(Counter);
Copy the code
Take a look at redux’s workflow:
- Call store.dispatch(action) to submit the action.
- The redux store calls the reducer function passed in. Pass in the current state and action.
- The root Reducer should merge multiple sub-Reducer outputs into a single state tree.
- The Redux store saves the complete state tree returned by the root Reducer.
HtmlWebpackPlugin optimization
We’ve been going through webpack before
contentBase: path.join(__dirname, '.. /dist'),
Copy the code
Configure to get dist/index.html for access. Need to write dead introduced JS, more troublesome. This plugin will automatically insert JS into your index.html template each time.
yarn add html-webpack-plugin -D
Copy the code
Then annotate the contentBase configuration of WebPack, create a new public directory under the root directory, move the index.html from dist to public, and remove the bundle.js reference
Then add the html-webpack-plugin configuration to webpack.dev.config.js.
const HtmlWebpackPlugin = require('html-webpack-plugin');
plugins: [
new HtmlWebpackPlugin({
filename: 'index.html',
template: path.join(__dirname, '.. /public/index.html')})]Copy the code
Next, we will use the html-webpack-plugin every time we start, and WebPack will automatically inject the packed JS into the index.html template.
Compiling CSS optimizations
The loader of CSS is introduced first
yarn add css-loader style-loader -D
Copy the code
Then add the index. CSS file to our previous Pages/Page directory and write a line of CSS
.page-box {
border: 1px solid red;
display: flex;
}
Copy the code
We then import and use it in page/index.js
import './index.css';
<div class="page-box">
this is Page~
</div>
Copy the code
Finally we made webpack support for loading CSS, added in webpack.dev.config.js rules
{
test: /\.css$/,
use: ['style-loader'.'css-loader']}Copy the code
After NPM start, view the page route to see the style in effect.
-
Css-loader enables you to use things like @import and URLS (…) Implement the function of require().
-
Style-loader adds all computed styles to the page; Together, the two enable you to embed stylesheets in webpack wrapped JS files.
Integrated PostCSS optimization
We added display:flex; Styles, usually when you’re writing CSS you need to prefix it with a browser. PostCSS provides a plugin for Autoprefixer to do this for us.
Import the related package YARN add postCSs-loader postCSs-cssnext -dCopy the code
Postcss – CssNext allows you to use future CSS features (including Autoprefixer).
Then configure webpack.dev.config.js
rules: [{
test: /\.(css)$/,
use: ["style-loader"."css-loader"."postcss-loader"]}]Copy the code
Then create postcss.config.js in the root directory
module.exports = {
plugins: {
'postcss-cssnext': {}}};Copy the code
Now you run the code, write a CSS, go to the browser and check the element to see if the attribute generated the browser prefix! . As follows:
Page-box {border: 1px solid red; display: flex; } page-box {border: 1px solid red; display: -webkit-box; display: -ms-flexbox; display: flex; }Copy the code
CSS Modules to optimize
CSS rules are global, and the style rules of any one component apply to the entire page. The only way to generate a local scope is to use the name of a class that is unique and does not overlap with other selectors. That’s how CSS Modules work.
We enable modules in webpack.dev.config.js
use: ['style-loader'.'css-loader? modules'.'postcss-loader']
Copy the code
Then when we introduce CSS, we can use objects. The form of an attribute. (Underline here, using [attribute name])
import style from './index.css';
<div className={style["page-box"]}>
this is Page~
</div>
Copy the code
When you open the console at this point, you’ll see that className becomes a hash string. And then we can beautify it so that we can use cssmodules and see what the original style was. Modify the CSS – loader
CSS - loader before? Modules after {loader:'css-loader',
options: {
modules: true.localIdentName: '[local]--[hash:base64:5]'}}Copy the code
Restart webpack, open the console, and find the class style changed to class=”page-box– 1wbXE “.
Compile picture optimization
Let’s start with the image loader
yarn add url-loader file-loader -D
Copy the code
Then create a new images directory under SRC and place an image a.jpg.
This is then configured in rules of webpack.dev.config.js, along with the images alias.
{
test: /\.(png|jpg|gif)$/,
use: [{
loader: 'url-loader',
options: {
limit: 8192
}
}]
}
images: path.join(__dirname, '.. /src/images'),
Copy the code
Options limit 8192 means that images less than or equal to 8K will be converted to Base64 encoding and inserted directly into HTML, reducing HTTP requests.
Then we continue on the previous page, importing the image and using it.
import pic from 'images/a.jpg'
<div className={style["page-box"]}>
this is Page~
<img src={pic}/>
</div>
Copy the code
Restart webpack and view the picture.
According to the need to load
We now start up and see that he loads a bundle.js file each time. When we load the first screen, it’s going to be slow. Since it downloads other things as well, we need something to differentiate what we need to load. At present, there are roughly divided into by route and by component. We use the usual load by route here. React – Router4.0 provides react-loadable.
Yarn add react-loadable -dCopy the code
Then rewrite our router.js
Import Home from'pages/home';
import Page from 'pages/page';
import Counter from 'pages/counter'; Then import loadable from'react-loadable';
import Loading from 'components/Loading';
const Home = loadable({
loader: () => import('pages/Home'),
loading: Loading,
timeout: 10000, // 10 seconds
})
const Page = loadable({
loader: () => import('pages/page'),
loading: Loading,
timeout: 10000, // 10 seconds
})
const Counter = loadable({
loader: () => import('pages/Counter'),
loading: Loading,
timeout: 10000, // 10 seconds
})
Copy the code
Loadable requires a loading component. Add a loading component under Components
import React from 'react';
export default () => {
return<div>Loading... </div> };Copy the code
We need Babel to support dynamic import. First introduced
yarn add @babel/plugin-syntax-dynamic-import -D
Copy the code
Then configure the babel.config.js file
plugins: ["@babel/plugin-syntax-dynamic-import"]
Copy the code
If you start the source file again, you will find that there is more than bundle.js in the source file. And every time you click on the routing menu, you will load the file of the menu, which is really loaded on demand.
Adding route 404
Create a notFound directory and 404 page component in the Pages directory
import React, {PureComponent} from 'react';
class NotFound extends PureComponent {
render() {
return (
<div>
404
</div>
)
}
}
export default NotFound;
Copy the code
Add route 404 to router.js
const NotFound = loadable({
loader: () => import('pages/notfound'),
loading: Loading,
timeout: 10000, // 10 seconds
})
<Switch>
<Route exact path="/" component={Home}/>
<Route path="/page" component={Page}/>
<Route path="/counter" component={Counter}/>
<Route component={NotFound}/>
</Switch>
Copy the code
Enter a route that does not exist and you will see the page component present as 404.
Extract common code
The react,redux,react-router, etc., are all reloaded every time we release them. We don’t need to extract them separately. Configure entry in webpack.dev.config.js:
entry: {
app:[
path.join(__dirname, '.. /src/index.js')
],
vendor: ['react'.'react-router-dom'.'redux'.'react-dom'.'react-redux']
},
output: {
path: path.join(__dirname, '.. /dist'),
filename: '[name].[hash].js',
chunkFilename: '[name].[chunkhash].js'
},
Copy the code
Extracting CSS files
We see that the source only has js files, but we actually have a CSS file, which is packaged into the JS file, now we extract it. Use webpack’s mini-CSS-extract-plugin.
yarn add mini-css-extract-plugin -D
Copy the code
Then configure it in Webpack
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
{
test: /\.css$/,
use: [{loader: MiniCssExtractPlugin.loader}, {
loader:'css-loader',
options: {
modules: true.localIdentName: '[local]--[hash:base64:5]'}},'postcss-loader']} new MiniCssExtractPlugin({// compress CSS filename:"[name].[contenthash].css",
chunkFilename: "[id].[contenthash].css"
})
Copy the code
After the restart, you will find an extra CSS file in the source, which proves that we have extracted successfully
The cache
We have hash, chunkhash, and contenthash in our output, so what are they for?
- Hash is related to the construction of the entire project. Whenever a file changes in the project, the hash value of the entire project will change and all files will share the same hash value
- Chunkhash is different from hash. It parses dependent files based on different Entry files, constructs corresponding chunks, and generates corresponding hash values.
- Contenthash is for file content level, the hash value will only change if the content of your own module changes, so we can resolve the appeal through contenthash
Production environment construction
The build goals for development and production environments are very different. In a development environment, we need source Map and LocalHost Server with real-time reload or hot module replacement capabilities. In the production environment, we shifted our focus to smaller bundles, lighter source maps, and more optimized resources to improve load times.
Create webpack.prod.config.js in the build directory, copy the original configuration and modify it. First remove MiniCssExtractPlugin from webpack.dev.config.js, then remove devServer from webpack.prod.config.js, and then modify the packaging command.
"build": "webpack --config ./build/webpack.prod.config.js"
Copy the code
Change devtool to None.
devtool: 'none'.Copy the code
Let’s do some more optimization for packaging.
File compression
In the past, Webpack used uglifyjs-webpack-plugin to compress files, which made our packaged files smaller.
Now you just need to configure mode to automatically use some of the configuration of the development environment, including JS compression and so on
mode:'production'.Copy the code
After packing, the volume becomes smaller greatly.
Common block extraction
This indicates which blocks will be selected for optimization. When supplied with a string, valid values are all, async, and initial. Providing all can be particularly powerful because it means that blocks can be shared even between asynchronous and non-asynchronous blocks.
optimization: {
splitChunks: {
chunks: 'all'}}Copy the code
Repackage, you will find that the packaging volume is smaller.
CSS compression
We found that with the mode configuration in production, JS is compressed, but CSS is not. Here we use the optimize- CSS -assets-webpack-plugin to compress CSS. Here are some suggestions from the website
Although WebPack 5 May have CSS Minimizer built in, you'll need to bring your own WebPack 4. To minimize the output, use a plug-in like optimize-CSS-assets-webpack-plugin. Setting optimization.minimizer overrides the default values provided by Webpack, so be sure to specify JS minimalizer as well:Copy the code
First introduced
yarn add optimize-css-assets-webpack-plugin -D
Copy the code
Add package configuration webpack.prod.config.js
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
plugins: [
...
new OptimizeCssAssetsPlugin()
],
Copy the code
Repackage, and you’ll find that the separate extracted CSS is also compressed.
Packaging to empty
We found that every time packaging, as long as the change will be added to the file, how to automatically empty the previous packaging content? Webpack provides the clean-Webpack-plugin plugin. First introduced
yarn add clean-webpack-plugin -D
Copy the code
Then configure the package file
const CleanWebpackPlugin = require('clean-webpack-plugin'); New CleanWebpackPlugin(), // clean it up before each packageCopy the code
public path
The publicPath configuration option is useful in a variety of scenarios. You can use it to specify the base path for all resources in your application. Added in the packaging configuration
output: {
publicPath : '/'
}
Copy the code
join @babel/polyfill, @babel/plugin-transform-runtime, core-js, @babel/runtime-corejs2, @babel/plugin-proposal-class-properties
yarn add @babel/polyfill -S
Copy the code
Add the following line to the entry of your WebPack configuration file:
/* entry/entry: {app:["@babel/polyfill",
path.join(__dirname, '.. /src/index.js')
],
vendor: ['react'.'react-router-dom'.'redux'.'react-dom'.'react-redux']},Copy the code
@babel/ Polyfill lets us happily use the browser incompatible ES6 and ES7 apis. But he has several shortcomings:
- One is that we only use a few apis, but it introduces the whole thing
- Second, it will pollute the whole situation
Now let’s do some optimization, add
Yarn add @babel/plugin-transform-runtime -d yarn add [email protected] -d yarn add @babel/plugin-proposal-class-properties -D yarn add @babel/runtime-corejs2 -SCopy the code
Json. Add browserslist to declare valid browsers
"browserslist": [
"1%" >."last 2 versions"].Copy the code
Modifying our Babel configuration file
{
presets: [["@babel/preset-env",{
useBuiltIns: "entry",
corejs: 2
}], "@babel/preset-react"],
plugins: ["@babel/plugin-syntax-dynamic-import".'@babel/plugin-transform-runtime'.'@babel/plugin-proposal-class-properties']}Copy the code
UseBuiltIns is a key attribute that polyfills on demand depending on whether BrowserList converts the new syntax to polyfill’s new API used by the new AP business code
- False: Disable polyfill. If import ‘@babel/polyfill’, all polyfills will be loaded regardless of browserList
- Entry: Enable. You need to manually import ‘@babel/ Polyfill ‘, which filters out the polyfills you want based on browserList
- Usage: manual import ‘@babel/polyfill’ is not required, and browserList + is used
Note: The test usage cannot support IE, so it is recommended to use Entry, although it will be tens of K larger.
@babel/plugin-transform-runtime and @babel/runtime-corejs2. The former is for development and the latter is for production. Main features: Avoid compiling helper functions more than once: Babel code needs helper functions to perform the same functions as the original code. You can also solve the problem of class or instance methods provided by @babel/ Polyfill contaminating the global scope.
@babel/plugin-proposal-class-properties is something I missed earlier. If you want to write arrow functions or decorators in a class, you need it.
Data request Axios and Mock
We now do completely separate applications, front-end write front-end, server write server, and they connect through an API. However, the server interface is often written so slowly that the front end cannot be debugged and can only wait. This is where we need our mock.js to provide the data ourselves. Mock.js will automatically intercept our Ajax requests and provide a variety of randomly generated data. (Be sure to comment out the proxy you started configuring, or we won’t be able to request our mock data.)
First install MockJS yarn add mockjs -dCopy the code
Then create a mock directory under the root directory and mock.js
import Mock from 'mockjs';
Mock.mock('/api/user', {
'name': '@cname'.'intro': '@word(20)'
});
Copy the code
Intercepting/API /user returns a random Chinese name as a string of 20 characters. Then introduce it in our SRC /index.js.
import '.. /mock/mock.js';
Copy the code
With the interface and data in place, let’s write a request to get the data and present it.
Yarn add axios -sCopy the code
Then create the Reducer, Action, and Page of userInfo respectively
Redux/actions/the userInfo. Js below import axios the from'axios';
export const GET_USER_INFO = "userInfo/GET_USER_INFO";
export function getUserInfo() {
return dispatch=>{
axios.post('/api/user').then((res)=>{
let data = JSON.parse(res.request.responseText);
dispatch({
type: GET_USER_INFO, payload:data }); }}})Copy the code
Redux/reducers/the userInfo. Js below import from {GET_USER_INFO}'actions/userInfo';
const initState = {
userInfo: {}
};
export default function reducer(state = initState, action) {
switch (action.type) {
case GET_USER_INFO:
return {
...state,
userInfo: action.payload,
};
default:
returnstate; }}Copy the code
Pages /userInfo/index.js import React, {PureComponent} from'react';
import {connect} from 'react-redux';
import {getUserInfo} from "actions/userInfo";
class UserInfo extends PureComponent {
render() {
const { userInfo={} } = this.props.userInfo;
return(< div > {< div > < p > user information: < / p > < p > username: {the userInfo. Name} < / p > < p > is introduced: {userinfo.intro}</p> </div>}< button onClick={() => this.props. GetUserInfo ()}>export default connect((userInfo) => userInfo, {getUserInfo})(UserInfo);
Copy the code
Then add our userInfo to the globally unique state, store,
store.js
import userInfo from 'reducers/userInfo';
let store = createStore(combineReducers({counter, userInfo}));
Copy the code
Finally, add new routes and menus
router.js
const UserInfo = loadable({
loader: () => import('pages/UserInfo'),
loading: Loading,
timeout: 10000, // 10 seconds
})
<Route path="/userinfo" component={UserInfo}/>
Copy the code
components/Nav/index.js
<li><Link to="/userinfo">UserInfo</Link></li>
Copy the code
Actions must be plain objects. Use custom middleware for async Actions. This indicates that actions must be an action object, and middleware must be used if asynchrony is to be used.
Story – thunk middleware
Let’s introduce it first
yarn add redux-thunk -S
Copy the code
We then use the applyMiddleware method provided by Redux to start the Redux-Thunk middleware and enable actions to support asynchronous functions.
import {createStore, applyMiddleware} from 'redux';
import thunkMiddleware from 'redux-thunk';
let store = createStore(combineReducers({counter, userInfo}), applyMiddleware(thunkMiddleware));
Copy the code
And then when we reboot, we get the data.
The deployment of
To test the feasibility of our packaged files, here is a small Express service. Create a server directory under the root directory and run the following command.
npm init
yarn add nodemon express -D
Copy the code
- Express is a relatively easy to use Node framework
- Nodemon is a node development aid that can update nodeJS code without restarting. After installing the dependencies, we added our express.js file to write to the Node service
var express = require('express');
var path = require('path');
var app = express();
app.get('/dist*'.function (req, res) {
res.sendFile( path.join(__dirname , ".. /" + req.url));
})
app.use(function (req, res) {
res.sendFile(path.join( __dirname , ".. /dist/" + "index.html" ));
})
var server = app.listen(8081, function () {
var host = server.address().address
var port = server.address().port
console.log("Application instance, access at http://%s:%s", host, port)
})
Copy the code
I won’t go into detail about the code for Node, but you can find tutorials online. Here we start a service with port 8081, and then do two intercepts. The first intercepts all access to dist*, forwarding it to the file packaged under our dist. The second interception intercepts any incorrect addresses and forwards them to our index.html, which solves the problem of refreshing 404.
Add the startup command to the server directory package.json file and execute it.
"test": "nodemon ./express.js"
Copy the code
npm run test
Copy the code
If you go to http://localhost:8081 after startup, you’ll find a number of modules that introduce 404. Don’t panic. Let’s change it to
publicPath : '/dist/'.Copy the code
After packaging once, we will find that everything is normal, our Node service is ready, and the packaged code can be used normally.
At the end
This is the end of the React bucket tutorial. The first time I wrote it, I didn’t sum it up very well. Without further ado, put some information for your reference.
- First of all, I would like to thank Brickspert for his “Build React Bucket Framework from Scratch” which inspired me a lot.
- bable
- react
- webpack
- Redux Chinese document
- axios
Special instructions
I am also a member of myriad front-end business, some questions asked to my knowledge blind area or no time to reply, please forgive me, thank you!!
In addition, this tutorial is designed for newcomers and other people who are new to the React technology stack to get a relatively comprehensive understanding of the React framework. Other optimizations and support are not added here.
It is recommended that this tutorial be used as a reference only and not as a quality development framework for projects.
The code itself will be tested again and uploaded to Github next week.