7

1. Introduction Introduce React and talk about their features

1.1 Introduction

React is a JavaScript library for building user interfaces and a UI-level solution. React follows component design patterns, declarative programming paradigms and functional programming concepts to make front-end application development more efficient. At the same time, React uses virtual DOM to effectively manipulate DOM, following a one-way data flow from high-level components to low-level components. At the same time, React can help us split the interface into individual small pieces. Each piece is a component. These components can be combined and nested to form a whole page.

Syntactically, the React component uses a method called render() or a function component return to receive the input data and return the content that needs to be displayed, such as:

class HelloMessage extends React.Component {
  render() {
    return (
      <div>
        Hello {this.props.name}
      </div>
    );
  }
}

ReactDOM.render(
  <HelloMessage name="Taylor" />,
  document.getElementById('hello-example')
);

The above-mentioned XML-like form is JSX, which will eventually be compiled into legal JS statements and called by babel. The passed data can be accessed in render() through this.props in the component.

1.2 Features

There are many features of React. Here are a few of them:

  • JSX syntax
  • One-way data binding
  • Virtual DOM
  • Declarative programming
  • Component

1.2.1 Declarative Programming

Declarative programming is a programming paradigm that focuses on what you want to do, not how to do it. It expresses logic without explicitly defining steps. This means that we need to declare the components to be displayed based on logical calculations, such as implementing a marked map: the steps of creating a map through commands, creating markers, and adding markers on the map are as follows.

// 创建地图
const map = new Map.map(document.getElementById('map'), {
    zoom: 4,
    center: {lat,lng}
});

// 创建标记
const marker = new Map.marker({
    position: {lat, lng},
    title: 'Hello Marker'
});

// 地图上添加标记
marker.setMap(map);

To achieve the above functions with React, it is as follows:

<Map zoom={4} center={lat, lng}>
    <Marker position={lat, lng} title={'Hello Marker'}/>
</Map>

Declarative programming makes React components easy to use, and the final code is simpler and easier to maintain.

1.2.2 Component

In React, everything is a component. The entire logic of the application is usually broken down into small individual parts. We call each individual part a component. The component can be a function or a class, accepts data input, processes it and returns the React elements presented in the UI. The functional components are as follows:

const Header = () => {
    return(
        <Jumbotron style={{backgroundColor:'orange'}}>
            <h1>TODO App</h1>
        </Jumbotron>
    )
}

For the need to change the state, the definition of the stateful component is as follows:

class Dashboard extends React.Component {
    constructor(props){
        super(props);

        this.state = {

        }
    }
    render() {
        return (
            <div className="dashboard"> 
                <ToDoForm />
                <ToDolist />
            </div>
        );
    }
}

As you can see, React components have the following characteristics:

  • can be combined with : a component is easy to use with other components, or nested inside another component.
  • reusable : Each component has an independent function, and it can be used in multiple UI scenarios.
  • can be maintained : Each small component only contains its own logic, which is easier to understand and maintain.

Second, the difference between Real DOM and Virtual DOM

2.1 Real DOM

Real DOM is relative to Virtual DOM. Real DOM refers to the document object model, which is an abstraction of structured text. Every node rendered on the page is a real DOM structure. We can use the browser's DevTool Check it out as follows.
在这里插入图片描述
Virtual Dom is essentially a description of the DOM in the form of a JavaScript object. The purpose of creating a virtual DOM is to better render virtual nodes into the page view. The nodes of the virtual DOM object correspond to the attributes of the real DOM one by one. In React, JSX is a major feature that allows you to directly declare the DOM structure of the interface in JS by using XML.

const vDom = <h1>Hello World</h1> // 创建h1标签,右边千万不能加引号
const root = document.getElementById('root') // 找到<div id="root"></div>节点
ReactDOM.render(vDom, root) // 把创建的h1标签渲染到root节点上

In the above code, ReactDOM.render() is used to insert the created virtual DOM node into a real node and render it on the page. In fact, JSX is a kind of syntactic sugar, which will be compiled and converted into JS code by babel during use. The above VDOM is converted into the following.

const vDom = React.createElement(
  'h1', 
  { className: 'hClass', id: 'hId' },
  'hello world'
)

As you can see, JSX is just to simplify calling React.createElement() directly:

  • The first parameter is the tag name, such as h1, span, table...
  • The second parameter is an object, which stores some attributes of the label, such as id, class, etc.
  • The third parameter is the text in the node.

Through the console.log (VDOM), you can get the relevant information of the virtual DOM.

在这里插入图片描述

Therefore, as can be seen from the above example, JSX is transformed into React.createElement by babel, and the return value is an object, which is the virtual DOM. ,

2.2 Difference

The difference between Real DOM and Virtual DOM is as follows:

  • The virtual DOM does not perform typesetting and redrawing operations, while the real DOM will frequently rearrange and redraw.
  • The total loss of virtual DOM is "virtual DOM addition, deletion and modification + real DOM difference addition, deletion and modification + typesetting and redrawing", and the total loss of real DOM is "real DOM complete addition, deletion and modification + typesetting and redrawing".

2.3 Advantages and disadvantages

Advantages of real DOM:

  • Easy to use

shortcoming:

  • The efficiency is low, the resolution speed is slow, and the memory usage is too high.
  • Poor performance: Frequent manipulation of the real DOM can easily lead to redrawing and reflow.

The advantages of using virtual DOM are as follows:

  • simple and convenient : If you use the manual operation of the real DOM to complete the page, it is cumbersome and error-prone, and it is difficult to maintain under large-scale applications.
  • Good performance: Using Virtual DOM can effectively avoid frequent updates of real DOM numbers, reduce redrawing and reflow caused by multiple times, and improve performance.
    -Cross-platform: React brings cross-platform capabilities with the help of virtual DOM, and a set of codes can run at multiple ends.

The disadvantages are as follows:

  • In some applications with extremely high performance requirements, the virtual DOM cannot be optimized specifically.
  • When rendering a large amount of DOM for the first time, the speed is slightly slower than normal due to the calculation of an additional layer of virtual DOM.

Three, what is the difference between super() and super(props)

3.1 ES6 category

In ES6, class inheritance is achieved through the extends keyword, as follows:

class sup {
    constructor(name) {
        this.name = name
    }

    printName() {
        console.log(this.name)
    }
}


class sub extends sup{
    constructor(name,age) {
        super(name) // super代表的事父类的构造函数
        this.age = age
    }

    printAge() {
        console.log(this.age)
    }
}

let jack = new sub('jack',20)
jack.printName()          //输出 : jack
jack.printAge()           //输出 : 20

In the above example, you can see that the super keyword is used to call the parent class. Super replaces the parent class's construction function. Using super(name) is equivalent to calling sup.prototype.constructor.call(this,name). If the super keyword is not used in the subclass, an error will be reported, as follows:
在这里插入图片描述
The reason for the error is that the subclass does not have its own this object, it can only inherit the this object of the parent class, and then process it. And super() inherits the this object in the parent class to the subclass. Without super(), the subclass will not get the this object. If you call this first, and then initialize super(), it is also prohibited.

class sub extends sup{
    constructor(name,age) {
        this.age = age
        super(name) // super代表的事父类的构造函数
    }
}

Therefore, in the subclass constructor, you must first substitute super to refer to this.

3.2 Class components

In React, the class component is implemented based on the es6 specification and inherits React.Component, so if you use the constructor, you must write super() to initialize this. At this time, when calling super(), we generally need to pass in props as a parameter. If it is not passed in, React will also define it in the component instance.

// React 内部
const instance = new YourComponent(props);
instance.props = props;

So regardless of whether there is a constructor or not, this.props can be used in render. This is automatically included with React and can be omitted.

class HelloMessage extends React.Component{
    render (){
        return (
            <div>nice to meet you! {this.props.name}</div>
        );
    }
}

But it is not recommended to use super() instead of super(props). Because React will assign a value to this.props after the class component constructor generates an instance, calling this.props will return undefined without passing props in super, as shown below.

class Button extends React.Component {
  constructor(props) {
    super();                             // 没传入 props
    console.log(props);          // {}
    console.log(this.props);  // undefined
   // ...
}

The incoming props can be accessed normally, ensuring that this.props has been assigned before the completion of the constructor, which is more logical.

class Button extends React.Component {
  constructor(props) {
    super(props); // 没传入 props
    console.log(props);      //  {}
    console.log(this.props); //  {}
  // ...
}

From the above example, we can draw:

  • In React, class components are based on ES6, so super must be used in the constructor.
  • When calling the super process, whether or not props are passed in, React internally assigns the props to the props property of the component instance.
  • If only super() is called, then this.props is still undefined between super() and the end of the constructor.

Fourth, talk about the setState execution mechanism

4.1 What is the setState mechanism

In React, the display form of a component can be determined by the data state and external parameters, and the data state is state. When you need to modify the state of the value inside, you need to call setState to change, so as to update the internal data of the component.

For example, the following example:

import React, { Component } from 'react'

export default class App extends Component {
    constructor(props) {
        super(props);

        this.state = {
            message: "Hello World"
        }
    }

    render() {
        return (
            <div>
                <h2>{this.state.message}</h2>
                <button onClick={e => this.changeText()}>面试官系列</button>
            </div>
        )
    }

    changeText() {
        this.setState({
            message: "JS每日一题"
        })
    }
}

The onclick event is triggered by clicking the button, the this.setState method is executed to update the state state, and then the render function is executed again, which causes the view of the page to be updated. If you want to directly modify the state of the state, you only need to call setState.

changeText() {
    this.state.message = "你好啊,世界";
}

We will find that there will be no response on the page, but the state of the state has changed. This is because React is not like calling Object.defineProperty data response in vue2 or Vue3 calling Proxy to monitor data changes. The setState method must be used to inform the react component that the state has changed.
The definition of the state method is inherited from React.Component. The source code of the definition is as follows:

Component.prototype.setState = function(partialState, callback) {
  invariant(
    typeof partialState === 'object' ||
      typeof partialState === 'function' ||
      partialState == null,
    'setState(...): takes an object of state variables to update or a ' +
      'function which returns an object of state variables.',
  );
  this.updater.enqueueSetState(this, partialState, callback, 'setState');
};

4.2 Update method

When using setState to update data, the update type of setState is divided into asynchronous update and synchronous update.

4.2.1 Asynchronous update

For example, there is the following piece of code.

changeText() {
  this.setState({
    message: "你好啊"
  })
  console.log(this.state.message);         // Hello World
}

The final print result of the above code is Hello world , and the latest state result cannot be obtained immediately after executing setState. If you want to get the updated value immediately, it will be executed after the update in the callback of the second parameter.

changeText() {
  this.setState({
    message: "你好啊"
  }, () => {
    console.log(this.state.message);   // 你好啊
  });
}

4.2.2 Synchronous update

The following is an example of using setTimeout to update synchronously.

changeText() {
  setTimeout(() => {
    this.setState({
      message: "你好啊
    });
    console.log(this.state.message); // 你好啊
  }, 0);
}

4.2.3 Batch update

Sometimes, we need to deal with the situation of batch update, first give an example:

handleClick = () => {
    this.setState({
        count: this.state.count + 1,
    })
    console.log(this.state.count) // 1

    this.setState({
        count: this.state.count + 1,
    })
    console.log(this.state.count) // 1

    this.setState({
        count: this.state.count + 1,
    })
    console.log(this.state.count) // 1
}

When we click the button to trigger the event, all printed are 1, and the page shows that the value of count is 2. SetState is performed multiple times on the same value, the batch update strategy of setState will overwrite it, and the result of the last execution will be taken. Therefore, the above code is equivalent to the following code:

Object.assign(  previousState,  {index: state.count+ 1},  {index: state.count+ 1},  ...)

Since the later data will overwrite the previous changes, it is only added once in the end. If the next state depends on the previous state, it is recommended to pass a function to setState as a parameter, as follows:

onClick = () => {    this.setState((prevState, props) => {      return {count: prevState.count + 1};    });    this.setState((prevState, props) => {      return {count: prevState.count + 1};    });}

In the setTimeout or native dom event, because it is a synchronous operation, there is no overwriting phenomenon.

Five, React event binding

5.1 Event binding

When we need to handle the click event, a few sentences need to add some binding operations to the event, the so-called event binding. The following is one of the most common event bindings:

class ShowAlert extends React.Component {
  showAlert() {
    console.log("Hi");
  }

  render() {
    return <button onClick={this.showAlert}>show</button>;
  }
}

As you can see, the event binding method needs to be wrapped in {} The above code seems to be no problem, but when you change the output code of the processing function to console.log(this) and click the button, you will find that the console outputs undefined.

5.2 Common binding methods

React's common event binding methods are as follows:

  • Use bind in the render method
  • Use arrow functions in the render method
  • bind in the constructor
  • Use arrow function binding in the definition phase

5.2.1 Use bind in the render method

If you use a class component and give a component/element an onClick attribute, it will now bind its this to the current component. The way to solve this problem is to use .bind(this) to change this after the event function. Bind to the current component.

class App extends React.Component {
  handleClick() {
    console.log('this > ', this);
  }
  render() {
    return (
      <div onClick={this.handleClick.bind(this)}>test</div>
    )
  }
}

In this way, every time the component renders, the bind operation will be repeated, which affects performance.

5.2.2 Use arrow functions in the render method

Bind the this point to the current component through the ES6 context, and also generate a new method every time it is rendered, which affects performance.

class App extends React.Component {
  handleClick() {
    console.log('this > ', this);
  }
  render() {
    return (
      <div onClick={e => this.handleClick(e)}>test</div>
    )
  }
}

5.2.3 bind in the constructor

Pre-bind the current component in the constructor can avoid repeated binding in the render operation.

class App extends React.Component {
  constructor(props) {
    super(props);
    this.handleClick = this.handleClick.bind(this);
  }
  handleClick() {
    console.log('this > ', this);
  }
  render() {
    return (
      <div onClick={this.handleClick}>test</div>
    )
  }
}

5.2.4 Use arrow function binding in the definition phase

Like the third method above, it can avoid repeated binding in the render operation, and the implementation is also very simple.

class App extends React.Component {
  constructor(props) {
    super(props);
  }
  handleClick = () => {
    console.log('this > ', this);
  }
  render() {
    return (
      <div onClick={this.handleClick}>test</div>
    )
  }
}

5.3 Difference

The main differences between the above four methods are as follows:

  • In terms of writing: Method one and method two are simple to write, and method three is too verbose
  • In terms of performance: Mode 1 and Mode 2 will generate a new method instance every time the component is rendered, and performance problems are lacking. If the function is passed to the child component as a property value, it will cause additional rendering. The third and fourth methods will only generate one method instance.

Based on the above, method four is the optimal event binding method.

Six, component communication in React

6.1 Component communication

Component is the core basic idea of Vue and React front-end framework, and it is also one of the most obvious features that distinguishes other js frameworks. Usually, a completed complex business page is composed of many basic components. The need to pass messages between components will involve communication. Communication means that the sender transmits information to the recipient through a certain medium and in a certain format to achieve a certain purpose. In a broad sense, any traffic of information is communication.

6.2 Several ways of communication

There are many ways of component delivery, which can be divided into the following types according to the sender and receiver:

  • The parent component passes to the child component
  • The child component passes to the parent component
  • Communication between sibling components
  • The parent component passes to the descendant components
  • Non-relational component delivery

6.2.1 The parent component passes messages to the child component

Since React's data flow is one-way, the most common way is to pass parent components to child components. When the parent component calls the child component, it only needs to pass the parameters in the child component tag, and the child component can receive the parameters passed by the parent component through the props attribute.

function EmailInput(props) {
  return (
    <label>
      Email: <input value={props.email} />
    </label>
  );
}

const element = <EmailInput email="123124132@163.com" />;

6.2.2 The child component passes messages to the parent component

The basic idea of the child component communicating with the parent component is that the parent component passes a function to the child component, and then through the callback of this function, the value passed by the child component is obtained. The corresponding code of the parent component is as follows:

class Parents extends Component {
  constructor() {
    super();
    this.state = {
      price: 0
    };
  }

  getItemPrice(e) {
    this.setState({
      price: e
    });
  }

  render() {
    return (
      <div>
        <div>price: {this.state.price}</div>
        {/* 向子组件中传入一个函数  */}
        <Child getPrice={this.getItemPrice.bind(this)} />
      </div>
    );
  }
}

The corresponding codes of the sub-components are as follows:

class Child extends Component {
  clickGoods(e) {
    // 在此函数中传入值
    this.props.getPrice(e);
  }

  render() {
    return (
      <div>
        <button onClick={this.clickGoods.bind(this, 100)}>goods1</button>
        <button onClick={this.clickGoods.bind(this, 1000)}>goods2</button>
      </div>
    );
  }
}

6.2.3 Communication between sibling components

If it is the transfer between sibling components, the parent component is used as the middle layer to realize data intercommunication, and the parent component is used to transfer.

class Parent extends React.Component {
  constructor(props) {
    super(props)
    this.state = {count: 0}
  }
  setCount = () => {
    this.setState({count: this.state.count + 1})
  }
  render() {
    return (
      <div>
        <SiblingA
          count={this.state.count}
        />
        <SiblingB
          onClick={this.setCount}
        />
      </div>
    );
  }
}

6.2.4 Inter-generational group delivery of messages

Passing data from parent components to descendant components is the most common thing, just like global data. Using context provides a way of communication between components, you can share data, and other data can read the corresponding data. Create a context by using React.createContext.

 const PriceContext = React.createContext('price')

After the context is successfully created, the Provider component is used to create the data source, and the Consumer component is used to receive data. The usage example is as follows: The Provider component is used to pass data to the descendant components through the value attribute.

<PriceContext.Provider value={100}>
</PriceContext.Provider>

If you want to get the data passed by the Provider, you can receive it through the Consumer component or using the contextType attribute. The corresponding values are as follows:

class MyClass extends React.Component {
  static contextType = PriceContext;
  render() {
    let price = this.context;
    /* 基于这个值进行渲染工作 */
  }
}

The Consumer component code is as follows:

<PriceContext.Consumer>
    { /*这里是一个函数*/ }
    {
        price => <div>price:{price}</div>
    }
</PriceContext.Consumer>

6.2.5 Non-relational components pass messages

If the type of relationship between the components is more complex, it is recommended to conduct a global resource management of the data to achieve communication, such as redux, mobx, etc.

Seven, React Hooks

7.1 Hook

Hook is a new feature of React 16.8. It allows you to use state and other React features without writing classes. As for why the hook is introduced, the official motivation is to solve the problems often encountered in the process of using and maintaining React for a long time, such as:

  • Difficult to reuse and share state-related logic in components
  • Components with complex logic are difficult to develop and maintain. When our components need to deal with multiple unrelated local states, each life cycle function may contain various unrelated logics.
  • This in the class component increases the cost of learning, and there are some problems with the optimization of the class component based on existing tools
  • Due to business changes, functional components had to be changed to class components, etc.

Functional components are also known as stateless components, and are only responsible for some of the work of rendering at the beginning. Therefore, the current functional component using Hook technology can also be a stateful component, and it can also maintain its own state and do some logical processing internally.

7.2 Hooks function

Hooks let our functional components have the characteristics of class components, such as the state and life cycle of the components. In order to realize state management, Hook provides many useful Hooks functions, the common ones are:

  • useState
  • useEffect
  • other
useState

First, give an example, as follows:

import React, { useState } from 'react';

function Example() {
  // 声明一个叫 "count" 的 state 变量
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p >
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

In the function component, the internal maintenance state of the function is implemented through useState. The parameter is the default value of the state, and the return value is an array. The first value is the current state, and the second value is the function to update the state. If the function component is implemented by a class component, the code is as follows:

class Example extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
  }

  render() {
    return (
      <div>
        <p>You clicked {this.state.count} times</p >
        <button onClick={() => this.setState({ count: this.state.count + 1 })}>
          Click me
        </button>
      </div>
    );
  }
}

From the above two code analysis, we can see the difference between the two:

  • State declaration method: Get directly through useState in the function component, and set the class component in the constructor through the constructor
  • State reading method: directly use variables in function components, and class components are obtained through this.state.count
  • State update method: update through setCount in function components, and through this.setState() in class components

In general, useState is more concise to use, reducing the ambiguity of this point.

useEffect

useEffect allows us to perform operations with side effects in function components. For example, the following is an example of a timer:

class Example extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
  }

  componentDidMount() {
    document.title = `You clicked ${this.state.count} times`;
  }
  componentDidUpdate() {
    document.title = `You clicked ${this.state.count} times`;
  }

  render() {
    return (
      <div>
        <p>You clicked {this.state.count} times</p >
        <button onClick={() => this.setState({ count: this.state.count + 1 })}>
          Click me
        </button>
      </div>
    );
  }
}

As you can see from the above, the component performs the same operation during the loading and updating phases. And if you use useEffect, you can extract the same logic, which is a method that the class component does not have.

import React, { useState, useEffect } from 'react';
function Example() {
  const [count, setCount] = useState(0);
 
  useEffect(() => {    document.title = `You clicked ${count} times`;  });
  return (
    <div>
      <p>You clicked {count} times</p >
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

The first parameter of useEffect accepts a callback function. By default, useEffect will be executed after the first rendering and update, which is equivalent to executing callbacks in the two life cycle functions of componentDidMount and componentDidUpdate.

If some specific value does not change between the two re-renders, you can skip the call to effect. At this time, you only need to pass in the second parameter, as follows:

useEffect(() => {
  document.title = `You clicked ${count} times`;
}, [count]);      // 仅在 count 更改时更新

After passing in the second parameter above, if the value of count is 5 and the count is still equal to 5 when our component is re-rendered, React will compare the [5] of the previous rendering with the [5] of the next rendering. If it is equal, skip the effects execution.

A cleanup function can be returned in the callback function. This is an optional cleanup mechanism for effect, which is equivalent to the componentwillUnmount lifecycle function in the class component. You can do some operations to clean up side effects, as follows:

useEffect(() => {
    function handleStatusChange(status) {
        setIsOnline(status.isOnline);
    }

    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    return () => {
        ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
});

It can be found that useEffect is equivalent to the combination of the three life cycle functions of componentDidMount, componentDidUpdate and componentWillUnmount.

Other hooks

In addition to the above two more common ones, React has many additional hooks.

  • useReducer
  • useCallback
  • useMemo
  • useRef

7.3 Summary

Through the preliminary understanding of the above, we can see that hooks can more easily solve the state-related reuse problem:

  • Each call to useHook will generate an independent state
  • We can better encapsulate our functions through custom hooks

Writing hooks is functional programming, each function is wrapped in a function, the overall style is more refreshing and more elegant.

8. Talk about your understanding of Redux

8.1 Concept

React is used to build user interfaces and help us solve the process of rendering DOM. There are many components in the entire application, and the state of each component is managed by itself, including the component defining its own state, communicating between components through props, and using Context to realize data sharing.

If each component is allowed to store its own related state, theoretically speaking, it will not affect the operation of the application, but in the development and subsequent maintenance phases, we will spend a lot of energy to query the process of state changes. In this case, if all the states are managed centrally, when the state needs to be updated, only this management needs to be centrally processed, and there is no need to care about how the state is distributed to each component.

Redux realizes the centralized management of state, and it needs to follow three basic principles when using it:

  • Single data source
  • state is read-only
  • Use pure functions to perform modifications

It should be noted that Redux is not only used in React, but also used with other interface libraries, such as Vue.

8.2 Working principle

Redux state management is mainly divided into three parts: Action Creactor, Store and Reducer. Among them, store is a public storage space for data. When a component changes the data content in the store, other components can perceive the change in the store, and then fetch the data, thus indirectly realizing the function of these data transfers.

The schematic diagram of the work flow is shown in the figure below.
在这里插入图片描述

The detailed introduction can be viewed: Redux's three core concepts

8.3 Use

First, you need to create a public data area of the store.

import { createStore } from 'redux' // 引入一个第三方的方法
const store = createStore() // 创建数据的公共存储区域(管理员)

Then, create a record book to assist in the management of data, that is, reduecer, which is essentially a function that receives two parameters state and action, and returns state.

// 设置默认值
const initialState = {
  counter: 0
}

const reducer = (state = initialState, action) => {
}

Next, use the createStore function to establish a connection between state and action, as follows.

const store = createStore(reducer)

If you want to get the data in the store, use store.getState() to get the current state, as follows.

console.log(store.getState());

Let's take a look at how to change the data in the store. The action is dispatched through dispatch, usually there is a type attribute in the action, and other data can also be carried.

store.dispatch({
  type: "INCREMENT"
})

store.dispath({
  type: "DECREMENT"
})

store.dispatch({
  type: "ADD_NUMBER",
  number: 5
})

Next, let's take a look at modifying the processing logic in the reducer.

const reducer = (state = initialState, action) => {
  switch (action.type) {
    case "INCREMENT":
      return {...state, counter: state.counter + 1};
    case "DECREMENT":
      return {...state, counter: state.counter - 1};
    case "ADD_NUMBER":
      return {...state, counter: state.counter + action.number}
    default: 
      return state;
  }
}

Note that reducer is a pure function and does not need to modify state directly. Then, after the action is dispatched, you can monitor store changes through store.subscribe.

store.subscribe(() => {
  console.log(store.getState());
})

In the React project, it will be used with react-redux.

const redux = require('redux');

const initialState = {
  counter: 0
}

// 创建reducer
const reducer = (state = initialState, action) => {
  switch (action.type) {
    case "INCREMENT":
      return {...state, counter: state.counter + 1};
    case "DECREMENT":
      return {...state, counter: state.counter - 1};
    case "ADD_NUMBER":
      return {...state, counter: state.counter + action.number}
    default: 
      return state;
  }
}

// 根据reducer创建store
const store = redux.createStore(reducer);

store.subscribe(() => {
  console.log(store.getState());
})

// 修改store中的state
store.dispatch({
  type: "INCREMENT"
})
// console.log(store.getState());

store.dispatch({
  type: "DECREMENT"
})
// console.log(store.getState());

store.dispatch({
  type: "ADD_NUMBER",
  number: 5
})
// console.log(store.getState());
  • createStore can help create a store.
  • store.dispatch helps to dispatch the action, and the action will be passed to the store.
  • The store.getState method can help get all the data content in the store.
  • The store.subscrible method subscribes to store changes. As long as the store changes, the callback function received by the store.subscrible function will be executed.

Nine, Redux middleware

9.1 What is middleware

Middleware is a type of software between the application system and the system software. It uses the basic services (functions) provided by the system software to connect various parts of the application system or different applications on the network to achieve resource sharing , The purpose of function sharing.

Earlier, we learned about the entire workflow of Redux. When the action is sent, the reducer immediately calculates the state. The entire process is a synchronous operation. Then if you need to support asynchronous operations, or support error handling, log monitoring, middleware can be used in this process.

In Redux, the middleware is placed in the dispatch process, and the interception processing is performed during the dispatch action, as shown in the following figure:
在这里插入图片描述
It's essentially a function, the store.dispatch method has been transformed, and other functions have been added between the two steps of issuing Action and executing Reducer.

9.2 Commonly used middleware

There are many excellent redux middleware, such as:

  • redux-thunk: for asynchronous operation
  • redux-logger: used for logging

The above middlewares need to be registered through applyMiddlewares. The function is to form an array of all middlewares, execute them in sequence, and pass them to createStore as the second parameter.

const store = createStore(
  reducer,
  applyMiddleware(thunk, logger)
);

9.2.1 redux-thunk

redux-thunk is an asynchronous processing middleware recommended by the official website. By default dispatch(action), action needs to be a JavaScript object.

The redux-thunk middleware will determine the type of data you are currently passing in. If it is a function, it will pass the parameter value (dispatch, getState) to the function.

  • The dispatch function is used for us to dispatch the action again later.
  • The getState function takes into account that some of our subsequent operations need to rely on the original state, so that we can get some of the previous state.

Therefore, dispatch can be written in the form of the following function.

const getHomeMultidataAction = () => {
  return (dispatch) => {
    axios.get("http://xxx.xx.xx.xx/test").then(res => {
      const data = res.data.data;
      dispatch(changeBannersAction(data.banner.list));
      dispatch(changeRecommendsAction(data.recommend.list));
    })
  }
}

9.2.2 redux-logger

If you want to implement a log function, you can use the ready-made redux-logger, as follows.

import { applyMiddleware, createStore } from 'redux';
import createLogger from 'redux-logger';
const logger = createLogger();

const store = createStore(
  reducer,
  applyMiddleware(logger)
);

9.3 Redux source code analysis

First, let's take a look at the source code of applyMiddlewares:

export default function applyMiddleware(...middlewares) {
  return (createStore) => (reducer, preloadedState, enhancer) => {
    var store = createStore(reducer, preloadedState, enhancer);
    var dispatch = store.dispatch;
    var chain = [];

    var middlewareAPI = {
      getState: store.getState,
      dispatch: (action) => dispatch(action)
    };
    chain = middlewares.map(middleware => middleware(middlewareAPI));
    dispatch = compose(...chain)(store.dispatch);

    return {...store, dispatch}
  }
}

As you can see, all middleware is put into an array chain, then nested execution, and finally store.dispatch is executed, and the middleware API (middlewareAPI) can get the two methods of getState and dispatch

Through the above analysis, we have learned the basic use of redux-thunk. At the same time, the dispatch will be judged internally, and then the corresponding operation will be executed. The principle is as follows:

function patchThunk(store) {
    let next = store.dispatch;

    function dispatchAndThunk(action) {
        if (typeof action === "function") {
            action(store.dispatch, store.getState);
        } else {
            next(action);
        }
    }

    store.dispatch = dispatchAndThunk;
}

Next, we implement a log output interception ourselves.

let next = store.dispatch;

function dispatchAndLog(action) {
  console.log("dispatching:", addAction(10));
  next(addAction(5));
  console.log("新的state:", store.getState());
}

store.dispatch = dispatchAndLog;

10. How to improve the rendering efficiency of components

We know that React is based on the perfect cooperation of virtual DOM and efficient Diff algorithm to achieve the smallest granularity update of DOM. In most cases, React's rendering efficiency of DOM is sufficient for our daily business. However, for complex business scenarios, performance issues still plague us. At this time, some measures need to be taken to improve running performance, and avoiding unnecessary rendering is one of the common optimization methods in business.

10.1 Implementation scheme

We have learned that the trigger timing of render is simply that the class component calls the setState method to cause the render. Once the parent component is rendered, the child component must also perform the render rendering. The rendering of the parent component causes the rendering of the child component, and the child component has not changed in any way. At this time, unnecessary rendering can be avoided. The specific implementation methods are as follows:

  • shouldComponentUpdate
  • PureComponent
  • React.memo

10.2 Involving life cycle functions

102.1 shouldComponentUpdate

Use the shouldComponentUpdate life cycle function to compare state and props to determine whether to re-render. By default, it returns true to indicate re-rendering. If you don’t want the component to re-render, just return false.

10.2.2 PureComponent

It is basically consistent with the principle of shouldComponentUpdate. ShouldComponentUpdate is implemented through a shallow comparison of props and state. The source code is roughly as follows:

if (this._compositeType === CompositeTypes.PureClass) {
    shouldUpdate = !shallowEqual(prevProps, nextProps) || ! shallowEqual(inst.state, nextState);
}

The source code of shallowEqual's corresponding method is as follows:

const hasOwnProperty = Object.prototype.hasOwnProperty;

/**
 * is 方法来判断两个值是否是相等的值,为何这么写可以移步 MDN 的文档
 * https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/is
 */
function is(x: mixed, y: mixed): boolean {
  if (x === y) {
    return x !== 0 || y !== 0 || 1 / x === 1 / y;
  } else {
    return x !== x && y !== y;
  }
}

function shallowEqual(objA: mixed, objB: mixed): boolean {
  // 首先对基本类型进行比较
  if (is(objA, objB)) {
    return true;
  }

  if (typeof objA !== 'object' || objA === null ||
      typeof objB !== 'object' || objB === null) {
    return false;
  }

  const keysA = Object.keys(objA);
  const keysB = Object.keys(objB);

  // 长度不相等直接返回false
  if (keysA.length !== keysB.length) {
    return false;
  }

  // key相等的情况下,再去循环比较
  for (let i = 0; i < keysA.length; i++) {
    if (
      !hasOwnProperty.call(objB, keysA[i]) ||
      !is(objA[keysA[i]], objB[keysA[i]])
    ) {
      return false;
    }
  }

  return true;
}

10.2.3 React.memo

React.memo is used to cache the rendering of components to avoid unnecessary updates. In fact, it is also a high-level component, very similar to PureComponent. But the difference is that React.memo can only be used for functional components.

import { memo } from 'react';

function Button(props) {
  // Component code
}

export default memo(Button);

If you need in-depth comparison, you can pass a comparison function to the second parameter of memo at this time.

function arePropsEqual(prevProps, nextProps) {
  // your code
  return prevProps === nextProps;
}

export default memo(Button, arePropsEqual);

10.3 Summary

In the actual development process, the front-end performance problem is a problem that must be considered. With the complexity of the business, the probability of encountering performance problems is also increasing.

In addition, it is recommended to make the page more granular. If one is too large, when the state is modified, it will cause the rendering of the entire large component. After the component is split, the granularity becomes smaller. Can reduce unnecessary rendering of sub-components.

11. Understanding of Fiber architecture

11.1 Background

The two threads of the JavaScript engine and the page rendering engine are mutually exclusive. When one thread executes, the other thread can only suspend and wait. If the JavaScript thread occupies the main thread for a long time, then the rendering level update has to wait for a long time. If the interface is not updated for a long time, the page responsiveness will deteriorate, and the user may feel stuck.

And this is exactly the problem faced by React 15's Stack Reconciler. When React is rendering components, the whole process from start to rendering is completed in one go and cannot be interrupted. If the component is large, the js thread will always execute, and then wait until the entire VDOM tree is calculated before it will be handed over to the rendering thread. This will result in some user interaction, animation and other tasks that cannot be processed immediately, leading to stuck conditions.

11.2 React Fiber

eact Fiber is a major change and optimization that Facebook spent more than two years on React. It is a re-implementation of React's core algorithm. It was confirmed from Facebook at the React Conf 2017 conference that React Fiber was released in React 16.

In React, the following operations are mainly done:

  • Priority is added to each, tasks with higher priority can interrupt tasks with lower priority. Then re-execute the low-priority tasks again.
  • Asynchronous tasks are added, requestIdleCallback api is called, and executed when the browser is idle.
  • The dom diff tree becomes a linked list, one dom corresponds to two fibers (a linked list), and two queues. This is to find the interrupted task and re-execute it.

From an architectural point of view, Fiber is a rewrite of React's core algorithm (that is, the reconciliation process). From a coding point of view, Fiber is a data structure defined within React. It is the node unit of the Fiber tree structure, which is the virtual DOM under the new React 16 architecture.

A fiber is a JavaScript object, which contains the information of the element, the update operation queue and type of the element, and its data structure is as follows:

type Fiber = {
  // 用于标记fiber的WorkTag类型,主要表示当前fiber代表的组件类型如FunctionComponent、ClassComponent等
  tag: WorkTag,
  // ReactElement里面的key
  key: null | string,
  // ReactElement.type,调用`createElement`的第一个参数
  elementType: any,
  // The resolved function/class/ associated with this fiber.
  // 表示当前代表的节点类型
  type: any,
  // 表示当前FiberNode对应的element组件实例
  stateNode: any,

  // 指向他在Fiber节点树中的`parent`,用来在处理完这个节点之后向上返回
  return: Fiber | null,
  // 指向自己的第一个子节点
  child: Fiber | null,
  // 指向自己的兄弟结构,兄弟节点的return指向同一个父节点
  sibling: Fiber | null,
  index: number,

  ref: null | (((handle: mixed) => void) & { _stringRef: ?string }) | RefObject,

  // 当前处理过程中的组件props对象
  pendingProps: any,
  // 上一次渲染完成之后的props
  memoizedProps: any,

  // 该Fiber对应的组件产生的Update会存放在这个队列里面
  updateQueue: UpdateQueue<any> | null,

  // 上一次渲染的时候的state
  memoizedState: any,

  // 一个列表,存放这个Fiber依赖的context
  firstContextDependency: ContextDependency<mixed> | null,

  mode: TypeOfMode,

  // Effect
  // 用来记录Side Effect
  effectTag: SideEffectTag,

  // 单链表用来快速查找下一个side effect
  nextEffect: Fiber | null,

  // 子树中第一个side effect
  firstEffect: Fiber | null,
  // 子树中最后一个side effect
  lastEffect: Fiber | null,

  // 代表任务在未来的哪个时间点应该被完成,之后版本改名为 lanes
  expirationTime: ExpirationTime,

  // 快速确定子树中是否有不在等待的变化
  childExpirationTime: ExpirationTime,

  // fiber的版本池,即记录fiber更新过程,便于恢复
  alternate: Fiber | null,
}

11.3 Solution

Fiber splits the rendering update process into multiple sub-tasks, each time only a small part is done, after finishing it, see if there is still time left, if there is any, continue to the next task; if not, suspend the current task and hand over the time control The main thread will continue execution when the main thread is not busy.

That is to say, it can be interrupted and resumed. After recovery, the previous intermediate state can be reused, and different tasks can be given different priorities. Each task update unit is the Fiber node corresponding to the React Element.

The above-mentioned method is realized by the requestIdleCallback method, and the window.requestIdleCallback() method queues the functions called during the idle period of the browser. This enables developers to perform background and low-priority work on the main event loop without affecting delayed key events such as animation and input response.

First of all, the task in React is divided into multiple steps and completed in batches. After completing part of the task, the control is returned to the browser, so that the browser has time to render the page. Waiting for the remaining time after the browser is busy, and then continuing the unfinished tasks of React before, is a kind of cooperative scheduling.

The implementation process is based on the Fiber node. As a static data structure, each Fiber node corresponds to a React element, which saves the type of the component (function component/class component/native component, etc.), the corresponding DOM node, etc. information. As a dynamic work unit, each Fiber node saves the changed state of the component and the work to be performed in this update.

Each Fiber node has a corresponding React element, and multiple Fiber nodes build a tree based on the following three attributes.

// 指向父级Fiber节点
this.return = null
// 指向子Fiber节点
this.child = null
// 指向右边第一个兄弟Fiber节点
this.sibling = null

12. What are the methods to optimize React performance?

12.1 Render

React has efficient performance by virtue of virtual DOM and diff algorithm, but in some cases, performance can obviously be further improved. We know that the class component will cause render by calling the setState method. Once the parent component is rendered, the child component must also perform the render rendering. When we want to update a sub-component, such as the content of the updated green part:
在这里插入图片描述
Ideally, we only call the render component under this path and perform the rendering of the corresponding component.
在这里插入图片描述
However, React's default approach is to call the render of all components, and then compare the generated virtual DOM.
在这里插入图片描述
Therefore, the default approach is very wasteful of performance.

12.2 Optimization plan

Wei Lai avoids unnecessary render. We introduced earlier that it can be optimized through shouldComponentUpdate, PureComponent, and React.memo. In addition, there are some common performance optimizations as follows:

  • Avoid using inline functions
  • Use React Fragments to avoid extra markup
  • Use Immutable
  • Lazy loading components
  • Event binding method
  • Server-side rendering

12.2.1 Avoid using inline functions

If we use inline functions, a new function instance will be created every time the render function is called, such as:

import React from "react";

export default class InlineFunctionComponent extends React.Component {
  render() {
    return (
      <div>
        <h1>Welcome Guest</h1>
        <input type="button" onClick={(e) => { this.setState({inputValue: e.target.value}) }} value="Click For Inline Function" />
      </div>
    )
  }
}

The correct approach is to create a function inside the component and bind the event to the function itself. In this way, a separate function instance will not be created every time render is called.

import React from "react";

export default class InlineFunctionComponent extends React.Component {
  
  setNewStateData = (event) => {
    this.setState({
      inputValue: e.target.value
    })
  }
  
  render() {
    return (
      <div>
        <h1>Welcome Guest</h1>
        <input type="button" onClick={this.setNewStateData} value="Click For Inline Function" />
      </div>
    )
  }
}

12.2.2 Use React Fragments to avoid extra markup

When users create new components, each component should have a single parent tag. The parent cannot have two tags, so there must be a common tag at the top, so we often add an extra tag div at the top of the component.

This extra label has no other purpose except serving as a parent label. At this time, you can use fraction. It does not introduce any additional tags to the component, but it can act as a parent tag.

export default class NestedRoutingComponent extends React.Component {
    render() {
        return (
            <>
                <h1>This is the Header Component</h1>
                <h2>Welcome To Demo Page</h2>
            </>
        )
    }
}

12.2.3 Lazy Loaded Components

From an engineering perspective, webpack has the ability to split code, which can create multiple packages for the application and dynamically load them at runtime to reduce the size of the initial package. In React, Suspense and lazy components are used to implement code splitting. The basic usage is as follows:

const johanComponent = React.lazy(() => import(/* webpackChunkName: "johanComponent" */ './myAwesome.component'));
 
export const johanAsyncComponent = props => (
  <React.Suspense fallback={<Spinner />}>
    <johanComponent {...props} />
  </React.Suspense>
);

12.2.4 Server-side rendering

Using the server-side rendering-side approach allows users to see the rendered page faster. Server-side rendering requires a node service. You can use express, koa, etc., to call react's renderToString method to render the root component into a string, and then output it to the response:

import { renderToString } from "react-dom/server";
import MyPage from "./MyPage";
app.get("/", (req, res) => {
  res.write("<!DOCTYPE html><html><head><title>My Page</title></head><body>");
  res.write("<div id='content'>");  
  res.write(renderToString(<MyPage/>));
  res.write("</div></body></html>");
  res.end();
});

Then, the client uses the render method to generate HTML.

import ReactDOM from 'react-dom';
import MyPage from "./MyPage";
ReactDOM.render(<MyPage />, document.getElementById('app'));

Thirteen, React server-side rendering

13.1 What is server-side rendering

Server rendering refers to the process of page processing technology that completes the splicing of the HTML structure of the page on the server side, sends it to the browser, and then binds status and events to it to become a fully interactive page. There are two main problems it solves:

  • SEO, because search engine crawlers can directly view the fully rendered page
  • Speed up the loading of the first screen and solve the problem of the first screen white screen

在这里插入图片描述

13.2 How to

In React, there are two main forms of implementing SSR:

  • Manually build an SSR framework
  • Use mature SSR framework, such as Next.JS

The following is to manually build an SSR framework to illustrate how to implement SSR. First, start an app.js file through express to listen for requests on port 3000. When the root directory is requested, HTML is returned, as follows:

const express = require('express')
const app = express()
app.get('/', (req,res) => res.send(`
<html>
   <head>
       <title>ssr demo</title>
   </head>
   <body>
       Hello world
   </body>
</html>
`))

app.listen(3000, () => console.log('Exampleapp listening on port 3000!'))

Then, write React code in the server and reference it in app.js:

import React from 'react'

const Home = () =>{

    return <div>home</div>

}

export default Home

In order for the server to recognize JSX, it is necessary to use webpakc to package and convert the project, create a configuration file webpack.server.js and perform related configurations, as shown below.

const path = require('path')    //node的path模块
const nodeExternals = require('webpack-node-externals')

module.exports = {
    target:'node',
    mode:'development',           //开发模式
    entry:'./app.js',             //入口
    output: {                     //打包出口
        filename:'bundle.js',     //打包后的文件名
        path:path.resolve(__dirname,'build')    //存放到根目录的build文件夹
    },
    externals: [nodeExternals()],  //保持node中require的引用方式
    module: {
        rules: [{                  //打包规则
           test:   /\.js?$/,       //对所有js文件进行打包
           loader:'babel-loader',  //使用babel-loader进行打包
           exclude: /node_modules/,//不打包node_modules中的js文件
           options: {
               presets: ['react','stage-0',['env', { 
                                  //loader时额外的打包规则,对react,JSX,ES6进行转换
                    targets: {
                        browsers: ['last 2versions']   //对主流浏览器最近两个版本进行兼容
                    }
               }]]
           }
       }]
    }
}

Then, with the help of react-dom, the renderToString method of server-side rendering is provided, which is responsible for parsing React components into Html.

import express from 'express'
import React from 'react'//引入React以支持JSX的语法
import { renderToString } from 'react-dom/server'//引入renderToString方法
import Home from'./src/containers/Home'

const app= express()
const content = renderToString(<Home/>)
app.get('/',(req,res) => res.send(`
<html>
   <head>
       <title>ssr demo</title>
   </head>
   <body>
        ${content}
   </body>
</html>
`))

app.listen(3001, () => console.log('Exampleapp listening on port 3001!'))

In the above process, the component has been successfully rendered onto the page. However, some event handling methods cannot be completed on the server side, so the component code needs to be executed again in the browser. This way of sharing a set of code between the server side and the client side is called isomorphism. In general, refactoring means that a set of React code runs on the server and then runs again when it arrives in the browser:

  • Server-side rendering completes the page structure
  • Browser-side rendering completes event binding

The way the browser implements event binding is to let the browser pull the JS file for execution, and let the JS code control it, so the script tag needs to be introduced. Introduce the react code executed by the client to the page through the script tag, and configure the route for the js file through the static middleware of express, and modify it as follows:

import express from 'express'
import React from 'react'//引入React以支持JSX的语法
import { renderToString } from'react-dom/server'//引入renderToString方法
import Home from './src/containers/Home'
 
const app = express()
app.use(express.static('public'));
//使用express提供的static中间件,中间件会将所有静态文件的路由指向public文件夹
 const content = renderToString(<Home/>)
 
app.get('/',(req,res)=>res.send(`
<html>
   <head>
       <title>ssr demo</title>
   </head>
   <body>
        ${content}
   <script src="/index.js"></script>
   </body>
</html>
`))

 app.listen(3001, () =>console.log('Example app listening on port 3001!'))

Then, execute the following react code on the client side, and create a new webpack.client.js as the webpack configuration file of t


xiangzhihong
5.9k 声望15.3k 粉丝

著有《React Native移动开发实战》1,2,3、《Kotlin入门与实战》《Weex跨平台开发实战》、《Flutter跨平台开发与实战》1,2和《Android应用开发实战》