React technical principles and code development practice: React elements and components

Author: Zen and the Art of Computer Programming

1. Background Introduction

React introduction

React (pronounced “reac?t”) is a JavaScript library for building user interfaces, launched by Facebook and open source. Its main feature is the declarative programming style, which makes the code more understandable, maintainable and reusable, so it is becoming more and more popular.

React’s componentization idea divides complex pages into multiple small and independent components. Only when these components are combined can complete complex functions. React itself provides componentization capabilities, but to build a complete application, it needs to work with other tools or frameworks, such as Flux or Redux to manage state, routing management, data flow management, etc.

React has attracted the favor of many programmers since its inception, including Facebook, Instagram, Netflix, Airbnb, GitHub, Google, Apple and other large companies. Its excellent performance has also been recognized by the industry and has become the cornerstone of many products. Today, React has been adopted by many well-known companies and organizations, such as Uber, Lyft, Snapchat, Discord, Khan Academy, etc.

Therefore, mastering React technology is of great help to programmers. If you don’t understand React and the principles and mechanisms behind it, it will be difficult to have an in-depth understanding and practice of it, and you will not be able to write high-quality, reliable code. Therefore, this article is based on the basic knowledge of React, and explains the core concepts, algorithm principles and component construction process of React from different perspectives, as well as how to apply it to actual projects.

Article Overview

This article will first give a preliminary introduction to some basic concepts, design patterns and data flow management methods of React. Then, through the process and key points of React component development, we analyze how React components construct a complete application step by step. Finally, we will combine practical examples to share experiences, lessons learned and expansion ideas in actual projects, hoping to bring some gains and inspiration to readers.

2. Core concepts and connections

JSX syntax

JSX (JavaScript XML) is an extension language with XML-like syntax. It is used to describe the structure, style and event handling of UI components. You can compile JSX into pure JavaScript code and run it directly in the browser. JSX syntax allows you to establish a clear isolation between the view layer and the business logic layer. JSX has the following features:

  1. Descriptive: JSX provides an HTML-like syntax to define the DOM structure. Therefore, people who read and modify JSX code can quickly understand its meaning.

  2. Compilable: JSX can be translated into standard JavaScript code by a compiler (such as Babel or TypeScript), so that it can be executed in a browser environment.

const element = <h1>Hello, world!</h1>;

 ReactDOM.render(
  element,
  document.getElementById('root')
);
  1. More features: JSX supports all JavaScript expressions, including conditionals, loops, function calls, and even operator overloading.

Virtual DOM

React uses Virtual DOM technology for rendering. Virtual DOM is a data structure that simulates the real DOM in memory. Each virtual node corresponds to a real node. When the virtual DOM is inconsistent with the real DOM, the minimum update path will be calculated and only the necessary parts will be updated to reduce DOM operations.

class Demo extends Component {
    constructor() {
        super();

        this.state = {
            count: 0
        };
    }

    componentDidMount() {
        setInterval(() => {
            this.setState({
                count: Math.random() * 100
            });
        }, 1000);
    }

    render() {
        return (
            <div style={<!-- -->{backgroundColor: '#fff', padding: '20px'}}>
                Hello World! Count: {this.state.count}
            </div>
        );
    }
}

The life cycle of the component is shown in the figure below:

Component Architecture Design Pattern

React provides two component architecture design patterns – class components and function components.

  1. Class components: Use ES6 classes to create components. Class properties and methods can be used in the render method and have complete life cycle methods.

  2. Functional components: Create components using simple functional forms. This approach does not require the creation of additional class instances, just returns a JSX template.

Data flow management method

React provides two data flow management methods – Flux and Redux.

Flux

Flux is a front-end architecture pattern introduced by Facebook. Flux divides each component of the application into four parts, namely Dispatcher, Store, View and ActionCreator.

Store

Store is where data is saved, and only Store can change the state. It can only trigger changes through actions.

{
   users: ['Alice', 'Bob']
}
ActionCreator

ActionCreator is the method that generates actions.

function addUser(name) {
   return {type: 'ADD_USER', payload: name};
}
Dispatcher

Dispatcher is responsible for distributing actions to corresponding stores.

dispatcher.dispatch(addUser('Charlie')); // action enters the queue and waits for processing
View

Views can only subscribe to state in the Store.

import { connect } from "react-redux";

const mapStateToProps = state => ({users: state.users});

@connect(mapStateToProps)
export default class UserList extends React.Component {
   render() {
      const {users} = this.props;

      return (
         <ul>
            {users.map((user, index) =>
               <li key={index}>{user}</li>)
            }
         </ul>
      );
   }
}

Redux

Redux is an architectural pattern based on the concept of Flux. Compared with Flux, Redux has some obvious improvements, such as forcing the use of a single data source, asynchronous processing, etc. Redux uses reducer functions to handle state changes.

Reducer

Reducer is a pure function that receives the old state and action and returns the new state.

// reducer function to handle ADD_USER action type
function userReducer(state = [], action) {
   switch (action.type) {
       case 'ADD_USER':
           return [...state, action.payload];
       default:
           return state;
   }
}
Store

Store is also a place to save data, the only difference is that it is an immutable object.

let store = createStore(userReducer);
Actions

Actions are methods for generating actions.

store.dispatch({type: 'ADD_USER', payload: 'Dave'}); // dispatch an action
Views

Views can only subscribe to state in the Store.

import { connect } from "react-redux";

const mapStateToProps = state => ({users: state});

@connect(mapStateToProps)
export default class UserList extends React.Component {
   render() {
      const {users} = this.props;

      return (
         <ul>
            {users.map((user, index) =>
               <li key={index}>{user}</li>)
            }
         </ul>
      );
   }
}

Component building process

When rendering, React will first check whether the component needs to be updated. If it finds that it needs to be updated, it will call the componentWillReceiveProps method to determine which props should be updated. Then it will call the shouldComponentUpdate method to determine whether to re-render the component. If it determines that it needs to be re-rendered, it will call the componentWillUpdate method to prepare the state when the component is about to be updated. After the component is ready, the render method will be called to generate a JSX template. After rendering, React will generate the corresponding tree structure based on JSX and compare it to find out what needs to be updated. Finally, React will call the componentDidUpdate method to notify the component that the update is complete.

componentWillMount(){
    console.log("Will Mount");
}

componentDidMount(){
    console.log("Did Mount");
}

shouldComponentUpdate(nextProps, nextState){
    if(JSON.stringify(this.props)!== JSON.stringify(nextProps)){
        return true;
    } else if(JSON.stringify(this.state)!== JSON.stringify(nextState)){
        return true;
    } else {
        return false;
    }
}

componentWillUpdate(nextProps, nextState){
    console.log("Will Update");
}

componentDidUpdate(prevProps, prevState){
    console.log("Did Update");
}

Create component

A component can be a simple function or a React.createClass object. But it is recommended to create a separate file as a container for the component.

import React from'react';

class Greeting extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}

export default Greeting;

In the above code, Greeting is a simple component that accepts a prop attribute name and then renders a piece of text that displays “Hello, [the value of the name property]”.

Normally, components should follow the single responsibility principle and be responsible for only one task. Therefore, it is recommended to split complex functionality into simple components.

import React from'react';

class Button extends React.Component {
  render() {
    return (
      <button onClick={() => this.props.onClick()}>{this.props.label}</button>
    )
  }
}

class Modal extends React.Component {
  render() {
    return (
      <div className="modal">
        <p>{this.props.message}</p>
        <Button label="Close" onClick={this.props.onClose}/>
      </div>
    )
  }
}

export default Modal;

In the above code, the Modal component contains two sub-components: Message and CloseButton. They are responsible for displaying the message content and buttons for closing the modal, respectively. The benefit of this is that the components can be tested and modified more easily because they are independent of each other. Moreover, if Message and CloseButton need to be modified, they can be modified separately without affecting the behavior of the Modal component.