React Family bucket from 0 to 1
This article explains how to build a complete React project from scratch. The core configuration of React, Webpack, Babel, React-Route, Redux and Redux-Saga will be explained in this paper. We hope that through this project, we can systematically understand the main knowledge of react stack and avoid forgetting the situation after building the stack once.
Code library: https://github.com/teapot-py/react-demo
Let’s start with a list of the major NPM package versions:
- [email protected]
- [email protected]
- babel@7+
- [email protected]
- redux@4+
Starting from the webpack
What does Webpack actually do? In fact, in simple terms, it is to start from the entry file, constantly looking for dependencies, at the same time to parse a variety of different files to load the corresponding loader, and finally generate the type of target file we want.
This process is like a treasure hunt in a maze, we from the entrance, at the same time we also will continue to receive message to the next place the treasure, we decode the information, and decoding time might need some tools, such as the key, and loader as the key, and then we can identify the content.
Back to our project, first initialize the project by executing the following commands respectively
Mkdir react-demo // Create a project folder. CD react-demo // CD Go to the project directory. NPM init // NPM is initializedCopy the code
The introduction of webpack
npm i webpack --save
touch webpack.config.jsCopy the code
Simply configure webpack and update webpack.config.js
const path = require('path'); Module.exports = {entry: './app.js', // export file output: {path: path.resolve(__dirname, 'dist'), // define output directory filename: 'my-first-webpack.bundle.js' // define the output file name}};Copy the code
Update package.json file to add webpack execution command to scripts
"scripts": {
"dev": "./node_modules/.bin/webpack --config webpack.config.js"
}Copy the code
If an error occurs, please install Webpack-CLI as prompted
npm i webpack-cliCopy the code
Perform webpack
npm run devCopy the code
If the dist file is generated under the project folder, our configuration is ok.
Access the react
Install act-related packages
npm install react react-dom --saveCopy the code
Update the app.js entry file
import React from 'react
import ReactDom from 'react-dom';
import App from './src/views/App';
ReactDom.render(<App />, document.getElementById('root'));Copy the code
Create a directory SRC /views/App. In the App directory, create an index.js file as the App component. The contents of the index.js file are as follows:
import React from 'react';
class App extends React.Component {
constructor(props) {
super(props);
}
render() {
return (<div>App Container</div>);
}
}
export default App;Copy the code
Create the template file index.html in the root directory
<! DOCTYPE html> <html> <head> <title>index</title> <meta charset="utf-8"> <meta name="viewport" content="width=device-width,initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no"> </head> <body> <div id="root"></div> </body> </html>Copy the code
React was introduced at this point, but there are still a lot of problems to be solved
- How to parse the code of the JS file?
- How do I add js files to template files?
Babel parses js files
Babel is a tool chain for converting ECMAScript2015+ code into backwards-compatible versions of JavaScript code in older browsers or environments.
Install babel-loader, @babel/core, @babel/preset-env, @babel/preset-react
npm i babel-loader@8 @babel/core @babel/preset-env @babel/preset-react -DCopy the code
- Babel-loader: a Webpack loader that uses Babel to convert JavaScript dependencies. Simply put, it is the middle layer between Webpack and Babel, allowing Webpack to parse JS files with bable when they are encountered
- @babel/core: babel-core, which converts ES6 code to ES5. After 7.0, the package name was upgraded to @babel/core. “@babel” is a kind of official symbol, a departure from the old days when people just gave names to each other.
- @babel/preset-env: Babel-preset -env, decide which mineralizations/plugins and polyfills to use depending on which browser you want to support, such as providing new features of modern browsers for old browsers.
- @babel/preset-react: that is, babel-preset-react, for all React plug-ins’ Babel preset, such as JSX conversion to functions.
Update webpack. Config. Js
Module: {rules: [{test: /\.js$/, // match.js file exclude: /node_modules/, use: {loader: 'babel-loader'}}]}Copy the code
Create and configure the. Babelrc file in the root directory
{
"presets": ["@babel/preset-env", "@babel/preset-react"]
}Copy the code
Configuration HtmlWebPackPlugin
The main purpose of this plug-in is to inject JS code into HTML files via <script> tags
npm i html-webpack-plugin -DCopy the code
Webpack added the HtmlWebPackPlugin configuration
At this point, let’s look at the complete structure of the webpack.config.js file
const path = require('path');
const HtmlWebPackPlugin = require('html-webpack-plugin');
module.exports = {
entry: './app.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'my-first-webpack.bundle.js'
},
mode: 'development',
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader'
}
}
]
},
plugins: [
new HtmlWebPackPlugin({
template: './index.html',
filename: path.resolve(__dirname, 'dist/index.html')
})
]
};Copy the code
Run NPM run start to generate the dist folder
The current directory structure is as follows
You can see that the dist file is added to create an index.html file. When we open the file in the browser, we can see the App component content.
Configuration webpack – dev – server
Webpack-dev-server can greatly improve our development efficiency by listening for file changes and automatically updating pages
Install webpack-dev-server as the dev dependency
npm i webpack-dev-server -DCopy the code
Update the package.json startup script
"Dev ": "webpack-dev-server --config webpack.config.js --open"Copy the code
New devServer configuration in webpack.config.js
DevServer: {hot: true, // contentBase: path.join(__dirname, 'dist'), // Server root compress: True, // enable gzip port: 8080, // port}, plugins: [new webpack HotModuleReplacementPlugin (), / / HMR allowed to update the various modules at runtime, without the need for full refresh new HtmlWebPackPlugin ({template: './index.html', filename: path.resolve(__dirname, 'dist/index.html') }) ]Copy the code
The introduction of story
Redux is a package for front-end data management. It avoids the problem that the front-end data cannot be managed due to the large project, and manages the front-end data state through a single data stream.
Create multiple directories
- Create a new SRC/Actions directory for creating action functions
- Create a new SRC/Reducers directory to create the reducers
- Create a SRC /store directory for creating stores
Let’s implement a counter function with Redux
Install dependencies
npm i redux react-redux -DCopy the code
Create the index.js file in the Actions folder
export const increment = () => {
return {
type: 'INCREMENT',
};
};
Copy the code
Create the index.js file under the Reducers folder
const initialState = {
number: 0
};
const incrementReducer = (state = initialState, action) => {
switch(action.type) {
case 'INCREMENT': {
state.number += 1
return { ...state }
break
};
default: return state;
}
};
export default incrementReducer;Copy the code
Update store. Js
import { createStore } from 'redux';
import incrementReducer from './reducers/index';
const store = createStore(incrementReducer);
export default store;
Copy the code
Update entry file app.js
import App from './src/views/App';
import ReactDom from 'react-dom';
import React from 'react';
import store from './src/store';
import { Provider } from 'react-redux';
ReactDom.render(
<Provider store={store}>
<App />
</Provider>
, document.getElementById('root'));Copy the code
Update App Components
import React from 'react'; import { connect } from 'react-redux'; import { increment } from '.. /.. /actions/index'; class App extends React.Component { constructor(props) { super(props); } onClick() {this.props. Dispatch (increment())} render() {return (<div> <div>current number: {this. Props. Number} < button onClick = {() = > enclosing onClick ()} > click + 1 < / button > < / div > < / div >). } } export default connect( state => ({ number: state.number }) )(App);Copy the code
Clicking on the number next to it keeps +1
Introduction of story – saga
Redux-saga keeps actions clean by listening on actions to execute tasks with side effects. The introduction of sagAS mechanisms and generator features makes it very easy for Redux-Saga to handle complex asynchronous problems.
The principle of redux-saga is actually very simple, by hijacking the asynchronous action, the asynchronous operation in redux-saga, after the asynchronous completion of the result to another action.
Let’s take our counter example and implement an asynchronous +1 operation.
Installing dependency packages
npm i redux-saga -DCopy the code
Create a new SRC /sagas/index.js file
import { delay } from 'redux-saga'
import { put, takeEvery } from 'redux-saga/effects'
export function* incrementAsync() {
yield delay(2000)
yield put({ type: 'INCREMENT' })
}
export function* watchIncrementAsync() {
yield takeEvery('INCREMENT_ASYNC', incrementAsync)
}Copy the code
To explain what you are doing, think of watchIncrementAsync as a saga that listens for an action named INCREMENT_ASYNC, and when INCREMENT_ASYNC is dispatched, The incrementAsync method is called, where an asynchronous operation is performed and the result is passed to an Action named INCREMENT to update the Store.
Update store. Js
Add redux-Saga middleware to store
import { createStore, applyMiddleware } from 'redux'; import incrementReducer from './reducers/index'; import createSagaMiddleware from 'redux-saga' import { watchIncrementAsync } from './sagas/index' const sagaMiddleware = createSagaMiddleware() const store = createStore(incrementReducer, applyMiddleware(sagaMiddleware)); sagaMiddleware.run(watchIncrementAsync) export default store;Copy the code
Update App Components
Add an asynchronous submit button on the page to observe the asynchronous result
import React from 'react'; import { connect } from 'react-redux'; import { increment } from '.. /.. /actions/index'; class App extends React.Component { constructor(props) { super(props); } onClick() { this.props.dispatch(increment()) } onClick2() { this.props.dispatch({ type: 'INCREMENT_ASYNC'})} render() {return (<div> <div>current number: {this.props. Number} <button onClick={()=> this.onclick ()}> </button></div> <div> {this. Props. Number} < button onClick = {() = > enclosing onClick2 ()} > click after 2 seconds + 1 < / button > < / div > < / div >). } } export default connect( state => ({ number: state.number }) )(App);Copy the code
The observation results show the following error:
This is due to the use of Generator functions in Redux-Saga. Our current Babel configuration does not support parsing Generator, so @babel/ plugin-transform-Runtime is required
npm install --save-dev @babel/plugin-transform-runtimeCopy the code
Babel-polyfill and transfor-Runtime are further explained here
babel-polyfill
By default, Babel only converts new JavaScript syntax, not new apis. For example, global objects such as Iterator, Generator, Set, Maps, Proxy, Reflect, Symbol, Promise, and some methods defined on global objects (such as Object.assign) do not translate. If you want to use these new objects and methods, you must use Babel-Polyfill to provide a shim for the current environment.
babel-runtime
Babel’s translated code performs the same functions as the source code by using helper functions that may be repeated in several modules, resulting in larger compiled code. To solve this problem, Babel provides a separate package, Babel-Runtime, for compiling modules to reuse utility functions. Libraries and toolkits generally do not introduce polyfills directly until babel-Runtime is used. Otherwise, global objects like Promises pollute the global namespace, requiring users of the library to provide polyfills themselves. These polyfills are usually mentioned in the library and tool instructions, for example, many libraries will require polyfills for ES5. After using babel-Runtime, libraries and tools simply add dependencies to package.json and let babel-Runtime import polyfills; Please refer to the detailed explanation
Babel Presets and plugins
The Babel plugin is usually broken down as small as possible and can be introduced as needed. For example, for ES6 to ES5 functions, Babel has officially split into 20+ plug-ins.
The benefits are obvious, both in terms of improved performance and scalability. For example, if a developer wants to experience the arrow functions of ES6, he can simply introduce transform-ES2015-arrow-Functions instead of loading an ES6 bucket.
But many times, plug-in by plug-in introduction is inefficient. For example, in a project where a developer wants to convert all of ES6 code to ES5, the plug-in by plug-in approach is maddeningly laborious and error-prone.
At this time, Babel Preset can be used.
The Babel Preset can simply be regarded as a set of the Babel Plugin. For example, babel-Preset – ES2015 contains all the plug-ins related to ES6 transitions.
Update. Babelrc file configuration to support Genrator
{
"presets": ["@babel/preset-env", "@babel/preset-react"],
"plugins": [
[
"@babel/plugin-transform-runtime",
{
"corejs": false,
"helpers": true,
"regenerator": true,
"useESModules": false
}
]
]
}Copy the code
Clicking the button will perform the +1 operation 2 seconds later.
The introduction of the react – the router
In web application development, routing system is an indispensable part. When the browser’s current URL changes, the routing system responds to keep the user interface in sync with the URL. With the advent of single-page application, front-end routing systems for it also appear. React -route is a front-end route that matches react.
Introducing the react – the router – the dom
npm install --save react-router-dom -DCopy the code
Update app.js entry file to add routing matching rules
import App from './src/views/App'; import ReactDom from 'react-dom'; import React from 'react'; import store from './src/store'; import { Provider } from 'react-redux'; import { BrowserRouter as Router, Route, Switch } from "react-router-dom"; Const About = () => <h2> page 1 </h2>; Const Users = () => <h2> page 2 </h2>; ReactDom.render( <Provider store={store}> <Router> <Switch> <Route path="/" exact component={App} /> <Route path="/about/" component={About} /> <Route path="/users/" component={Users} /> </Switch> </Router> </Provider> , document.getElementById('root'));Copy the code
Update App components to display routing effects
import React from 'react'; import { connect } from 'react-redux'; import { increment } from '.. /.. /actions/index'; import { Link } from "react-router-dom"; class App extends React.Component { constructor(props) { super(props); } onClick() { this.props.dispatch(increment()) } onClick2() { this.props.dispatch({ type: 'INCREMENT_ASYNC'})} render() {return (<div> <div>react-router test </div> <nav> <ul> <li> <Link To = "/ about/" > page 1 < / Link > < / li > < li > < Link to ="/users/" > page 2 < / Link > < / li > < / ul > < nav > < br / > < div > redux & Redux-saga test </div> <div>current number: {this.props. Number} <button onClick={()=> this.onclick ()}> </button></div> <div> {this. Props. Number} < button onClick = {() = > enclosing onClick2 ()} > click after 2 seconds + 1 < / button > < / div > < / div >). } } export default connect( state => ({ number: state.number }) )(App);Copy the code
Click the list to jump to related routes
conclusion
Now that we’ve built a simple but fully functional React project, let’s review what we did
- The introduction of webpack
- The introduction of the react
- Introduce Babel to parse React
- Access to Webpack-dev-Server improves front-end development efficiency
- Redux is introduced to implement a increment function
- Redux-saga is introduced for asynchronous processing
- React-router was introduced to implement front-end routing
Small as it is, the React tool chain has all the tools in it. I want to quickly understand the React tool chain with the simplest code. In fact, this small project still has a lot of imperfections, such as style parsing, Eslint checking, production environment configuration, although these are indispensable parts of a complete project, but in the case of the demo project, it may interfere with our understanding of the React tool chain, so we will not add it to the project. I’ll create a new branch later to add all of these features, while also optimizing the current directory structure. Code library: https://github.com/teapot-py/react-demo