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:

  1. [email protected]
  2. [email protected]
  3. babel@7+
  4. [email protected]
  5. 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

  1. How to parse the code of the JS file?
  2. 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
  1. 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
  2. @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.
  3. @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.
  4. @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

  1. Create a new SRC/Actions directory for creating action functions
  2. Create a new SRC/Reducers directory to create the reducers
  3. 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

  1. The introduction of webpack
  2. The introduction of the react
  3. Introduce Babel to parse React
  4. Access to Webpack-dev-Server improves front-end development efficiency
  5. Redux is introduced to implement a increment function
  6. Redux-saga is introduced for asynchronous processing
  7. 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