2021 react the way of learning

1, Create react app

Global install create react app

$ npm install -g create-react-app

Create a project

$ create-react-app your-app Pay attention to the naming method

Creating a new React app in /dir/your-app.

Installing packages. This might take a couple of minutes. The installation process is slow,
Installing react, react-dom, and react-scripts... 

If you don't want to install globally, you can use npx directly

$ npx create-react-app your-app	The same effect can be achieved

This takes a while, and the process actually installs three things

  • React: the top-level Library of react
  • React DOM: because react has many running environments, such as react native on the app side, we use react DOM to run on the web
  • React scripts: contains all scripts and configurations for running and packaging react applications

The following interface appears, indicating that the project is created successfully:

Success! Created your-app at /dir/your-app
Inside that directory, you can run several commands:

  npm start
    Starts the development server.

  npm run build
    Bundles the app into static files for production.

  npm test
    Starts the test runner.

  npm run eject
    Removes this tool and copies build dependencies, configuration files
    and scripts into the app directory. If you do this, you can't go back!

We suggest that you begin by typing:

  cd your-app
  npm start

Happy hacking!

According to the above prompt, enter the directory through CD your app command and run npm start to run the project.

The directory structure of the generated project is as follows:

├── README.md							Documentation of usage methods
├── node_modules					All dependent installation directories
├── package-lock.json			Lock the version number of the package at the time of installation,Ensure that the team's dependence can ensure consistency.
├── package.json					
├── public								Static public directory
└── src										Source code directory for development

common problem:

  • npm installation failed

    • Switch to npm image and Taobao image
    • If you still fail to use yarn, you have to switch the source of yarn to China
    • If there is no solution, please delete node_modules and package lock JSON and re execute the npm install command
    • If it can't be solved, delete the node_modules and package lock JSON while clearing the npm cache, npm cache clean --force, and then execute the npm install command
  • yarn

    • Install npm install -g yarn
    • View version yarn --version
    • Set Taobao source yarn config set registry https://registry.npm.taobao.org -g

2, About React

1. The origin and development of React

React originated from Facebook's internal project. Because the company was not satisfied with all JavaScript MVC frameworks on the market, it decided to write one for Instagram website. After making it, I found that this set of things is very easy to use, and it was opened in May 2013.

2. Relationship between React and traditional MVC

Lightweight view layer library! A JavaScript library for building user interfaces

React is not a complete MVC framework. It can be considered as V (View) in MVC at most. Even react does not very recognize MVC development mode; React builds the library of page UI. It can be simply understood that react divides the interface into independent blocks. Each block is a component. These components can be combined and nested to become our page.

3. Reflection of high performance of React: virtual DOM

Principle of React high performance:

In Web development, we always need to reflect the changed data to the UI in real time. At this time, we need to operate the dom. Complex or frequent DOM operations are often the cause of performance bottlenecks (how to carry out high-performance complex DOM operations is usually an important indicator to measure the skills of a front-end developer).

React introduces the mechanism of Virtual DOM: it implements a set of DOM API with Javascript on the browser side. When developing based on react, all DOM structures are constructed through Virtual DOM. Whenever the data changes, react will rebuild the whole DOM tree, and then react will compare the current whole DOM tree with the last DOM tree to get the difference of DOM structure, and then only update the actual browser dom of the part that needs to be changed. Moreover, react can batch process the refresh of Virtual DOM, and the two data changes in an Event Loop will be merged. For example, if you continuously change the node content from A-B to B-A, react will think that a becomes B, and then from B to a, and the UI will not change. If it is controlled manually, this logic is usually extremely complex.

Although a complete virtual DOM tree needs to be constructed every time, because the virtual DOM is memory data, the performance is very high, while the operation on the actual DOM is only Diff points, so it can improve the performance. In this way, while ensuring performance, developers will no longer need to pay attention to how a data change is updated to one or more specific DOM elements, but only how the whole interface is rendered in any data state.

React Fiber:

A react core algorithm released after react 16. React Fiber is a re implementation of the core algorithm (according to the official website). The diff algorithm was used before.

In previous React, the update process was synchronous, which may lead to performance problems.

When React decides to load or update the component tree, it will do many things, such as calling the life cycle function of each component, calculating and comparing the Virtual DOM, and finally updating the DOM tree. The whole process is synchronous, that is, as long as a loading or updating process starts, it will not be interrupted in the middle. Because of the single thread feature of JavaScript, if each synchronization task takes too long when the component tree is large, it will get stuck.

The method of React Fiber is actually very simple - slicing. Each task will be divided into a small piece of time, but it will not take a long time for each other to run. However, it will not take a long time for each other to run.

4. Features and advantages of React

(1) Virtual DOM

We used to operate DOM through document Getelementbyid(), which is actually to read the DOM structure of html, convert the structure into variables, and then operate. reactjs defines a set of DOM model in the form of variables, and all operations and conversions are directly in variables. In this way, the real dom of operation is reduced, and the performance is quite high. It is essentially different from the mainstream MVC framework and does not deal with dom

(2) Component system

The core idea of react is that any area or element in the page can be regarded as a component

So what is a component?

Component refers to a polymer containing html, css, js and image elements at the same time

The core of using react development is to split the page into several components, and one component of react is coupled with css, js and image at the same time. This mode completely subverts the traditional way in the past

(3) Unidirectional data flow

In fact, the core content of reactjs is data binding. The so-called data binding means that as long as some server-side data are bound with the front-end page, developers only focus on the realization of business

(4) JSX syntax

In vue, we use the render function to build the dom structure of the component, which has high performance, because the process of finding and compiling the template is omitted. However, when using createElement to create the structure in render, the code readability is low and complex. At this time, we can use jsx syntax to create dom in render to solve this problem, but the premise is to use tools to compile jsx

3, Write the first react application

Multiple dependent files need to be introduced for react development: react js,react-dom.js, there are development versions and production versions respectively. Create react app has helped us install these things. Clear the src directory under the project directory created through CRA, and then re create an index js. Write the following code:

// React is introduced from the package of react. As long as you want to write react JS components must introduce react, because there is a syntax in react called JSX. We will talk about JSX later. To write JSX, we must introduce react
import React from 'react'
// ReactDOM can help us render the react component to the page. There is no other function. It was introduced from react DOM, not from react.
import ReactDOM from 'react-dom'

// There is a render method in ReactDOM. Its function is to render components and construct a DOM tree, and then insert them into a specific element on the page
ReactDOM.render(
// It's strange here. It's not a string. It looks like pure HTML code written in JavaScript code. Grammatical errors? This is not legal JavaScript code. The syntax of "tags written in JavaScript" is called JSX- JavaScript XML.
  <h1>Welcome to React The world of</h1>,
// Where to render
  document.getElementById('root')
)

4, Elements and components

If there is too much code, it is impossible to write it in the render method all the time, so you need to bring out the code inside and define a variable, like this:

import React from 'react'
import ReactDOM from 'react-dom'
// I'm not used to it here again? This is to define the react element with JSX
const app = <h1>Welcome to React The world of</h1>
ReactDOM.render(
  app,
  document.getElementById('root')
)

1. Functional component

Since the element has no way to pass parameters, we need to change the previously defined variable into a method to return an element:

import React from 'react'
import ReactDOM from 'react-dom'

// Pay special attention to the writing method here. If you want to write js expressions in JSX (only expressions, not process control), you need to add {}, including comments. It can be nested at multiple levels
const app = (props) => <h1>Welcome to{props.name}The world of</h1>

ReactDOM.render(
  app({
    name: 'react'
  }),
  document.getElementById('root')
)

The method we define here is actually the first way to define components in react - to define functional components, which are also stateless components. However, this writing method does not conform to the jsx style of react. A better way is to use the following methods for transformation

import React from 'react'
import ReactDOM from 'react-dom'

const App = (props) => <h1>Welcome to{props.name}The world of</h1>

ReactDOM.render(
  // Call method of React component
  <App name="react" />,
  document.getElementById('root')
)

Such a complete functional component is defined. But be careful! be careful! be careful! The component name must be capitalized, otherwise an error will be reported.

2. class component

The addition of ES6 enables JavaScript to directly support the use of class to define a class. The second way to create a component in react is to inherit the class used. ES6 class is currently officially recommended. It is built using ES6 standard syntax. See the following code:

import React from 'react'
import ReactDOM from 'react-dom'

class App extends React.Component {
  render () {
    return (
      // Note that you have to use this here props. Name, you must use this props
      <h1>Welcome to{this.props.name}The world of</h1>
  	)
  }
}
ReactDOM.render(
  <App name="react" />,
  document.getElementById('root')
)

The operation result is exactly the same as before, because there is no real class in JS. This class is just a syntax sugar, but the operation mechanism of the two is different from the underlying operation mechanism.

  • Functional components are called directly, which has been seen in the previous code

  • The es6 class component is actually a constructor. Every time a component is used, it is equivalent to instantiating a component, like this:

    import React from 'react'
    import ReactDOM from 'react-dom'
    
    class App extends React.Component {
      render () {
        return (
      		<h1>Welcome to{this.props.name}The world of</h1>
      	)
      }
    }
    
    const app = new App({
      name: 'react'
    }).render()
    
    ReactDOM.render(
      app,
      document.getElementById('root')
    )
    

3. An older way

Previous versions of 16 also supported the creation of components in this way, but current projects basically do not use it

React.createClass({
  render () {
    return (
      <div>{this.props.xxx}</div>
  	)
  }
})

4. Combination and nesting of components

When a component is rendered to a node, the original content of the node will be overwritten

The way of component nesting is to write the child components into the template of the parent component, and react does not have the content distribution mechanism (slot) in Vue, so we can only see the parent-child relationship in the template of a component

// React and react are introduced from react package JS Component parent class Component
// A react A special component Fragment in JS
import React, { Component, Fragment } from 'react'
import ReactDOM from 'react-dom'

class Title extends Component {
  render () {
    return (
      <h1>Welcome to React The world of</h1>
  	)
  }
}
class Content extends Component {
  render () {
    return (
      <p>React.js Is a build UI Library of</p>
  	)
  }
}
/** Since each React component can only have one root node, when rendering multiple components, you need to package a container in the outermost layer. If you use div, an extra layer of dom will be generated
class App extends Component {
  render () {
    return (
    	<div>
    		<Title />
        <Content />
      </div>
  	)
  }
}
**/
// If you don't want to generate an extra layer of dom, you can wrap it in the outermost layer using the Fragment component provided by React
class App extends Component {
  render () {
    return (
      <Fragment>
      	<Title />
        <Content />
      </Fragment>
  	)
  }
}
ReactDOM.render(
  <App/>,
  document.getElementById('root')
)

5, JSX principle

To understand the principle of JSX, you need to understand how to use JavaScript objects to represent the structure of a DOM element?

Look at the DOM structure below

<div class='app' id='appRoot'>
  <h1 class='title'>Welcome to React The world of</h1>
  <p>
    React.js Is a page that helps you build UI Library of
  </p>
</div>

All the above HTML information can be represented by JavaScript objects:

{
  tag: 'div',
  attrs: { className: 'app', id: 'appRoot'},
  children: [
    {
      tag: 'h1',
      attrs: { className: 'title' },
      children: ['Welcome to React The world of']
    },
    {
      tag: 'p',
      attrs: null,
      children: ['React.js Is a build page UI Library of']
    }
  ]
}

But it's too long to write in JavaScript, and the structure doesn't look clear. It's much easier to write in HTML.

So react JS extends the syntax of JavaScript, so that the JavaScript language can support the syntax of directly writing HTML tag structure in JavaScript code, which is much more convenient to write. The compilation process will convert the JSX structure similar to HTML into the object structure of JavaScript.

The following code:

import React from 'react'
import ReactDOM from 'react-dom'

class App extends React.Component {
  render () {
    return (
      <div className='app' id='appRoot'>
        <h1 className='title'>Welcome to React The world of</h1>
        <p>
          React.js Is a build page UI Library of
        </p>
      </div>
    )
  }
}

ReactDOM.render(
	<App />,
  document.getElementById('root')
)

After compiling, you will get such code:

import React from 'react'
import ReactDOM from 'react-dom'

class App extends React.Component {
  render () {
    return (
      React.createElement(
        "div",
        {
          className: 'app',
          id: 'appRoot'
        },
        React.createElement(
          "h1",
          { className: 'title' },
          "Welcome to React The world of"
        ),
        React.createElement(
          "p",
          null,
          "React.js Is a build page UI Library of"
        )
      )
    )
  }
}

ReactDOM.render(
	React.createElement(App),
  document.getElementById('root')
)

React.createElement will build a JavaScript object to describe the information of your HTML structure, including tag name, attributes, and child elements. The syntax is

React.createElement(
  type,
  [props],
  [...children]
)

The so-called JSX is actually a JavaScript object, so when using React and JSX, you must go through the compilation process:

JSX - use react to construct components and bable to compile - > JavaScript object - reactdom Insert - > DOM element - > render

6, DOM style in component

  • Inline style

To add inline styles to the virtual dom, you need to use expressions to pass in style objects:

// Notice the two parentheses here. The first one indicates that we want to insert JS into JSX, and the second one is the parenthesis of the object
 <p style={{color:'red', fontSize:'14px'}}>Hello world</p>

In line style needs to be written into a style object, and the location of this style object can be placed in many places, such as render function, component prototype and outer chain js file

  • Use class

React recommends that we use inline style, because react thinks that each component is an independent whole

In fact, in most cases, we are still adding a lot of class names to elements, but it should be noted that class needs to be written as className (because after all, we are writing class js code and will receive the current js rules, and class is the keyword)

<p className="hello" style = {this.style}>Hello world</p>
  • Add different styles for different conditions

Sometimes different styles need to be added according to different conditions, such as: completion status, completion is green, unfinished is red. In this case, we recommend it classnames This package:

  • css-in-js

Styled components is a set of css in js framework written for React, which is simply to write css in js. npm link

7, Component data mounting method

1. Properties (props)

Props is normally imported from the outside, and the settings can also be initialized in some ways inside the component. The properties cannot be changed by the component itself, but you can import new props by actively re rendering the parent component

Attributes are used to describe properties and characteristics. Components cannot change them at will.

The previous component code contains the simple use of props. Generally speaking, when using a component, you can put parameters in the attributes of the tag, and all attributes will be used as the key value of the component props object. Components created through the arrow function need to receive props through the parameters of the function:

import React, { Component, Fragment } from 'react'
import ReactDOM from 'react-dom'

class Title extends Component {
  render () {
    return (
  		<h1>Welcome to{this.props.name}The world of</h1>
  	)
  }
}

const Content = (props) => {
  return (
    <p>{props.name}Is a build UI Library of</p>
  )
}

class App extends Component {
  render () {
    return (
  		<Fragment>
      	<Title name="React" />
        <Content name="React.js" />
      </Fragment>
  	)
  }
}

ReactDOM.render(
	<App/>,
  document.getElementById('root')
)

(1) Set default props for components

import React, { Component, Fragment } from 'react'
import ReactDOM from 'react-dom'

class Title extends Component {
  // For the component created using class, write the static method directly here and create defaultProps
  static defaultProps = {
    name: 'React'
  }
  render () {
    return (
  		<h1>Welcome to{this.props.name}The world of</h1>
  	)
  }
}

const Content = (props) => {
  return (
    <p>{props.name}Is a build UI Library of</p>
  )
}

// The attribute of prodefaults component needs to be written directly on this component
Content.defaultProps = {
  name: 'React.js'
}

class App extends Component {
  render () {
    return (
  		<Fragment>
        {/* Since defaultProps is set, it can operate normally without transmitting props. If it is transmitted, the value of defaultProps will be overwritten */}
      	<Title />
        <Content />
      </Fragment>
  	)
  }
}

ReactDOM.render(
	<App/>,
  document.getElementById('root')
)

(2) props.children

We know that when using components, they can be nested. To use nested structures in custom components, you need to use props children . In practical work, we need to write components in this way almost every day.

import React, { Component, Fragment } from 'react'
import ReactDOM from 'react-dom'

class Title extends Component {
  render () {
    return (
  		<h1>Welcome to{this.props.children}The world of</h1>
  	)
  }
}

const Content = (props) => {
  return (
    <p>{props.children}</p>
  )
}

class App extends Component {
  render () {
    return (
  		<Fragment>
      	<Title>React</Title>
        <Content><i>React.js</i>Is a build UI Library of</Content>
      </Fragment>
  	)
  }
}

ReactDOM.render(
	<App/>,
  document.getElementById('root')
)

(3) Check props using prop types

React is actually born to build large-scale applications. In a large-scale application, you don't know what parameters will be passed in when others use the components you write. It may cause the application to fail to run, but no error will be reported. In order to solve this problem, react provides a mechanism so that the person who writes the component can set parameter check for the props of the component, which needs to be installed and used prop-types:

$ npm i prop-types -S

2. Status (state)

Status is the data that the component describes a certain display situation. It is set and changed by the component itself, that is, it is maintained by the component itself. The purpose of using status is to make the display of components different in different states (self-management)

(1) Define state

The first way

import React, { Component } from 'react'
import ReactDOM from 'react-dom'

class App extends Component {
  state = {
    name: 'React',
    isLiked: false
  }
  render () {
    return (
      <div>
        <h1>Welcome to{this.state.name}The world of</h1>
        <button>
          {
            this.state.isLiked ? '❤️cancel' : '🖤Collection'
          }
        </button>
      </div>
  	)
  }
}
ReactDOM.render(
	<App/>,
  document.getElementById('root')
)

Another way (recommended)

import React, { Component } from 'react'
import ReactDOM from 'react-dom'

class App extends Component {
  constructor() {
    super()
    this.state = {
      name: 'React',
      isLiked: false
    }
  }
  render () {
    return (
  		<div>
        <h1>Welcome to{this.state.name}The world of</h1>
        <button>
          {
            this.state.isLiked ? '❤️cancel' : '🖤Collection'
          }
        </button>
      </div>
  	)
  }
}
ReactDOM.render(
  <App/>,
  document.getElementById('root')
)

this.props and this State is a pure js object. In vue, the data attribute uses object The data getter and setter will be triggered when changing the data of {data processed by defineproperty, but such processing is not done in react. If it is changed directly, react cannot know. Therefore, it is necessary to use a special method to change the state, setState.

(2) setState

isLiked is stored in the state object of the instance. In the render function of the component, the content of "Cancel" or "collection" will be displayed according to the isLiked in the state of the component. Click event monitoring is added to the button below.

import React, { Component } from 'react'
import ReactDOM from 'react-dom'

class App extends Component {
  constructor() {
    super()
    this.state = {
      name: 'React',
      isLiked: false
    }
  }
  handleBtnClick = () => {
    this.setState({
      isLiked: !this.state.isLiked
    })
  }
  render () {
    return (
      <div>
        <h1>Welcome to{this.state.name}The world of</h1>
        <button onClick={this.handleBtnClick}>
          {
            this.state.isLiked ? '❤️cancel' : '🖤Collection'
          }
        </button>
      </div>
  	)
  }
}
ReactDOM.render(
	<App/>,
  document.getElementById('root')
)

setState has two parameters

The first parameter can be an object or a method return object. We call this parameter updater

  • Parameters are objects

    this.setState({
      isLiked: !this.state.isLiked
    })
    
  • Parameters are methods

    this.setState((prevState, props) => {
      return {
        isLiked: !prevState.isLiked
      }
    })
    

    Note that this method receives two parameters, the first is the last state and the second is props

setState is asynchronous, so if you want to get the latest state, there is no way to get it. You have the second parameter, which is an optional callback function

this.setState((prevState, props) => {
  return {
    isLiked: !prevState.isLiked
  }
}, () => {
  console.log('In callback',this.state.isLiked)
})
console.log('setState External',this.state.isLiked)

3. Attribute vs status

Similarities: they are all pure js objects, which will trigger the update of render and have certainty (the same status / attribute and the same result)

difference:

  1. The property can be obtained from the parent component, and the state cannot be changed
  2. The property can be modified by the parent component, and the status cannot be changed
  3. Property can set default values internally, and the status can also be changed
  4. The attribute is not modified inside the component, and the status needs to be changed
  5. Property can set the initial value of sub components, but not the status
  6. Property can modify the value of sub components, but the status cannot

The main function of state is to save, control and modify its own variable state. State is initialized inside the component and can be modified by the component itself, but it cannot be accessed or modified outside. You can think of state as a local data source that can only be controlled by the component itself. Status in state can be accessed through this Update the setState method, which will cause the re rendering of components.

The main function of props is to allow the parent component using the component to pass in parameters to configure the component. It is a configuration parameter passed in from the outside, which cannot be controlled or modified inside the component. Unless an external component actively passes in a new props, the props of the component will remain unchanged forever.

If you can't figure out the usage scenarios of state and props, remember a simple rule: use less state and more props.

A component without a state is called a stateless component, and a component with a state set is called a stateful component. Because the state will bring the complexity of management, we should write as many stateless components as possible and as few stateful components as possible. This will reduce the difficulty of code maintenance and enhance the reusability of components to a certain extent.

4. State promotion

If multiple components share one data, the data is managed in a common parent component

5. Controlled and uncontrolled components

Whether the data rendering of the React component is completely controlled by the props passed by the caller is a controlled component, otherwise it is an uncontrolled component.

6. Render data

  • conditional rendering

    {
      condition ? '❤️cancel' : '🖤Collection'
    }
    
  • List rendering

// data
const people = [{
  id: 1,
  name: 'Leo',
  age: 35
}, {
  id: 2,
  name: 'XiaoMing',
  age: 16
}]
// Render list
{
  people.map(person => {
    return (
      <dl key={person.id}>
        <dt>{person.name}</dt>
        <dd>age: {person.age}</dd>
      </dl>
    )
  })
}

The efficiency of React depends on the so-called virtual DOM, and try not to touch the dom. There is a problem with list elements: elements may change position in a list. To achieve this operation, you only need to exchange the DOM position, but React doesn't know that we just changed the position of the element, so it will re render the next two elements (and then execute virtual DOM), which will greatly increase the DOM operation. However, if you add a unique identifier to each element, React can know that the two elements only exchange positions. This identifier is the key, which must be the unique identifier of each element

  • dangerouslySetInnerHTML

For the content created by rich text, the data obtained in the background is as follows:

content = "<p>React.js Is a build UI Library of</p>"

For security reasons, the contents of all expressions in React will be escaped. If entered directly, the label will be regarded as text. At this time, we need to use the dangerouslySetHTML attribute, which allows us to dynamically set innerHTML

import React, { Component } from 'react'
import ReactDOM from 'react-dom'

class App extends Component {
  constructor() {
    super()
    this.state = {
      content : "<p>React.js Is a build UI Library of</p>"
    }
  }
  render () {
    return (
  		<div
        // Notice that here are two underscores__ html
        dangerouslySetInnerHTML={{__html: this.state.content}}
      />
  	)
  }
}
ReactDOM.render(
	<App/>,
  document.getElementById('root')
)

8, Event handling

1. Binding event

Use on + event name to bind an event. Note that this is different from the original event. The original event is all lowercase onClick, the event in React is hump onClick, and the event in React is not the original event, but the composite event.

2. Writing method of event handler

  • Write the arrow function in the line directly in render (not recommended)
  • Use the arrow function to define a method within the component (recommended)
  • Directly define a method of non arrow function in the component, and then directly use onClick={this.handleClick.bind(this)} in render (not recommended)
  • Directly define a method of non arrow function in the component, and then bind (this) in the constructor (recommended)

3. Event object

Like ordinary browsers, the event handler will be automatically passed into an event object, which is basically consistent with the methods and properties contained in ordinary browser event objects. The difference is that the event object in React is not provided by the browser, but built internally. It also has event stopPropagation,event.preventDefault is a common method

4. Parameter passing of event

  • There is a layer of arrow function outside the place where the method is called in render
  • Pass this in render handleEvent. Bind (this, parameter) is passed in this way
  • Pass through event
  • It is recommended to make a sub component, define the method in the parent component, pass it to the sub component through props, and then pass it to the sub component through this props. Method to call

5. Process user input

import React, { Component } from 'react'
import ReactDOM from 'react-dom'

class App extends Component {
  constructor() {
    super()
    this.state = {
      xing: '',
      ming: ''
    }
  }
  handleInputChange = (e) => {
    this.setState({
      [e.target.name]: e.target.value
    })
  }
  render () {
    const {
      xing,
      ming
    } = this.state
    return (
  		<div>
        <label>
          <span>surname:</span>
          <input
            type="text"
            name="xing"
            value={xing}
            onChange={this.handleInputChange}
          />
        </label>
        <label>
          <span>name:</span>
          <input
            type="text"
            name="ming"
            value={ming}
            onChange={this.handleInputChange}
          />
        </label>
        <p>Welcome: {xing}{ming}</p>
      </div>
  	)
  }
}
ReactDOM.render(
	<App/>,
  document.getElementById('root')
)

9, Form

In React, HTML form elements work differently from other DOM elements because form elements usually maintain some internal state s. For example, this pure HTML form accepts only one name:

<form>
  <label>
    name:
    <input type="text" name="name" />
  </label>
  <input type="submit" value="Submit" />
</form>

This form has the default HTML form behavior, that is, browse to a new page after the user submits the form. If you execute the same code in React, it still works. But in most cases, using JavaScript functions can easily handle the submission of forms and access the form data filled in by users. The standard way to achieve this effect is to use "controlled components".

1. Controlled components

In HTML, form elements such as,, and usually maintain their own state and update according to user input. In React, mutable state is usually saved in the state attribute of the component and can only be updated by using setState().

We can combine the two to make React's state the "only data source". The React component of the rendered form also controls what happens to the form during user input. The form input elements whose values are controlled by React in this way are called "controlled components".

For example, if we want the previous example to print out the name when submitting, we can write the form as a controlled component:

class NameForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = {value: ''};

    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  handleChange(event) {
    this.setState({value: event.target.value});
  }

  handleSubmit(event) {
    alert('Submitted name: ' + this.state.value);
    event.preventDefault();
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          name:
          <input type="text" value={this.state.value} onChange={this.handleChange} />
        </label>
        <input type="submit" value="Submit" />
      </form>
    );
  }
}

Since the value attribute is set on the form element, the displayed value will always be this state. Value, which makes React's state the only data source. Since handlechange executes and updates the state of React every time you press a key, the displayed value will be updated with user input.

For controlled components, the input value is always driven by React's state. You can also pass value to other UI elements or reset it through other event handlers, but that means you need to write more code.

2. textarea label

In HTML, < textarea > element defines its text through its child elements:

<textarea>
  Hello, this is text area Text in
</textarea>

In React, < textarea > uses the value attribute instead. In this way, the form using < textarea > can be very similar to the form using single line input:

class EssayForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      value: 'Please write an article about what you like DOM Element article.'
    };

    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  handleChange(event) {
    this.setState({value: event.target.value});
  }

  handleSubmit(event) {
    alert('Submitted articles: ' + this.state.value);
    event.preventDefault();
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          article:
          <textarea value={this.state.value} onChange={this.handleChange} />
        </label>
        <input type="submit" value="Submit" />
      </form>
    );
  }
}

Please note that this state. Value is initialized in the constructor, so the text area has an initial value by default.

3. select tag

In HTML, < Select > creates a drop-down list tag. For example, the following HTML creates a fruit related drop-down list:

<select>
  <option value="grapefruit">Grapefruit</option>
  <option value="lime">Lime</option>
  <option selected value="coconut">Coconut</option>
  <option value="mango">Mango</option>
</select>

Note that the coconut option is selected by default due to the selected attribute. Instead of using the selected attribute, React uses the value attribute on the root select tag. This is more convenient in controlled components because you only need to update it in the root tag. For example:

class FlavorForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = {value: 'coconut'};

    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  handleChange(event) {
    this.setState({value: event.target.value});
  }

  handleSubmit(event) {
    alert('What's your favorite flavor: ' + this.state.value);
    event.preventDefault();
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          Choose the flavor you like:
          <select value={this.state.value} onChange={this.handleChange}>
            <option value="grapefruit">Grapefruit</option>
            <option value="lime">Lime</option>
            <option value="coconut">Coconut</option>
            <option value="mango">Mango</option>
          </select>
        </label>
        <input type="submit" value="Submit" />
      </form>
    );
  }
}

In general, this makes tags such as < input type = "text" >, < textarea > and < Select > very similar - they all accept a value attribute that you can use to implement controlled components.

be careful

You can pass the array to the value attribute to support multiple options in the select tag:

<select multiple={true} value={['B', 'C']}>
class MulFlavorForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      value: "coconut",
      arr: [],
      options: [
        { value: "grapefruit", label: "Grapefruit" },
        { value: "lime", label: "Lime" },
        { value: "coconut", label: "Coconut" },
        { value: "mango", label: "Mango" }
      ]
    };

    this.handleChange = this.handleChange.bind(this);
  }

  handleChange(e){
    let idx = this.state.arr.findIndex(item=>{
      return item === e.target.value
    })
    if (idx >= 0) {
      this.state.arr.splice(idx,1);
    } else {
      this.state.arr.push(e.target.value);
    }
    let arr = this.state.arr;
    this.setState({arr});
  }

  render() {
    return (
      <div>
        <select multiple={true} value={this.state.arr} onChange={this.handleChange}>
          {this.state.options.map((item,index) => {
            return <option value={item.value} key={index}>{item.label}</option>;
          })}
        </select>
      </div>
    );
  }
}

export default Test4;

4. Process multiple inputs

When we need to process multiple input elements, we can add the name attribute to each element and let the processing function according to event target. The value of name selects the action to be performed.

class Reservation extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      isGoing: true,
      numberOfGuests: 2
    };

    this.handleInputChange = this.handleInputChange.bind(this);
  }

  handleInputChange(event) {
    const target = event.target;
    const value = target.type === 'checkbox' ? target.checked : target.value;
    const name = target.name;

    this.setState({
      [name]: value
    });
  }

  render() {
    return (
      <form>
        <label>
          participate in:
          <input
            name="isGoing"
            type="checkbox"
            checked={this.state.isGoing}
            onChange={this.handleInputChange} />
        </label>
        <br />
        <label>
          Number of guests:
          <input
            name="numberOfGuests"
            type="number"
            value={this.state.numberOfGuests}
            onChange={this.handleInputChange} />
        </label>
      </form>
    );
  }
}

5. File input tag

In HTML, < input type = "file" > allows users to select one or more files from the storage device and upload them to the server, or control them by using the File API of JavaScript.

<input type="file" />

Because its value is read-only, it is an uncontrolled component in React. It will be discussed with other uncontrolled components in subsequent documents.

6. Controlled input null

A prop that specifies value on a controlled component prevents the user from changing the input. If you specify value but the input can still be edited, you may accidentally set value to undefined or null.

The following code demonstrates this. (the input is initially locked, but becomes editable after a short delay.)

ReactDOM.render(<input value="hi" />, mountNode);

setTimeout(function() {
  ReactDOM.render(<input value={null} />, mountNode);
}, 1000);

7. Uncontrolled components

In most cases, we recommend using Controlled components To process form data. In a controlled component, the form data is managed by the React component. Another alternative is to use uncontrolled components, where the form data will be handled by DOM nodes.

To write an uncontrolled component instead of writing a data processing function for each status update, you can Using ref To get the form data from the DOM node.

For example, the following code uses an uncontrolled component to accept the value of a form:

class NameForm extends React.Component {
  constructor(props) {
    super(props);
    this.handleSubmit = this.handleSubmit.bind(this);
    this.input = React.createRef();
  }

  handleSubmit(event) {
    alert('A name was submitted: ' + this.input.current.value);
    event.preventDefault();
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          Name:
          <input type="text" ref={this.input} />
        </label>
        <input type="submit" value="Submit" />
      </form>
    );
  }
}

Because uncontrolled components store real data in DOM nodes, it is sometimes easier to integrate React and non React code at the same time when using uncontrolled components. If you don't mind the beauty of your code and want to write code quickly, using uncontrolled components can often reduce the amount of your code. Otherwise, you should use controlled components.

(1) Default value

During the React rendering life cycle, the value on the form element will override the value in the DOM node. In uncontrolled components, you often want React to give the component an initial value, but do not control subsequent updates. In this case, you can specify a defaultValue attribute instead of value.

render() {
  return (
    <form onSubmit={this.handleSubmit}>
      <label>
        Name:
        <input
          defaultValue="Bob"
          type="text"
          ref={this.input} />
      </label>
      <input type="submit" value="Submit" />
    </form>
  );
}

Similarly, < input type = "checkbox" > and < input type = "radio" > support defaultChecked, < Select > and < textarea > support defaultValue.

(2) File input

In HTML, < input type = "file" > allows users to select one or more files to upload to the server, or by using File API Operate.

In React, < input type = "file" > is always an uncontrolled component, because its value can only be set by the user, not controlled by code.

You should use the File API to interact with files. The following example shows how to create a ref of DOM node So as to obtain the information of the file when submitting the form.

class FileInput extends React.Component {
  constructor(props) {
    super(props);
    this.handleSubmit = this.handleSubmit.bind(this);
    this.fileInput = React.createRef();
  }
  handleSubmit(event) {
    event.preventDefault();
    alert(
      `Selected file - ${this.fileInput.current.files[0].name}`
    );
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          Upload file:
          <input type="file" ref={this.fileInput} />
        </label>
        <br />
        <button type="submit">Submit</button>
      </form>
    );
  }
}

ReactDOM.render(
  <FileInput />,
  document.getElementById('root')
);

10, TodoList

The basic directory structure of the components in the project development is basically as follows:

/your-project

  • src
    • ...
    • components
      • YourComponentOne
        • index.js/YourComponentOne.js
      • YourComponentTwo
        • index.js/YourComponentTwo.js
      • index.js is used to export components

Note: a component only does one thing, so TodoList and TodoItem should be made into two components, which is also convenient for later understanding of shouldComponentUpdate

11, Component lifecycle

Components in React also have a life cycle, that is, there are many hook functions for us to use. The life cycle of components will be divided into four stages: initialization, running, destruction and error handling (after 16.3)

1. Initialize

It is executed during the component initialization phase

  1. constructor

  2. static getDerivedStateFromProps()

  3. componentWillMount() / UNSAFE_componentWillMount()

  4. render()

  5. componentDidMount()

2. Update phase

The change of props or state may cause the update of components. The following methods will be called in the process of component re rendering:

1. componentWillReceiveProps() / UNSAFE_componentWillReceiveProps() 
2. static getDerivedStateFromProps()
3. shouldComponentUpdate() 
4. componentWillUpdate() / UNSAFE_componentWillUpdate() 
5. render() 
6. getSnapshotBeforeUpdate() 
7. componentDidUpdate()

3. Unloading phase

  1. componentWillUnmount()

4. Error handling

  1. componentDidCatch()

5. Detailed explanation of each life cycle

(1) constructor(props)

The constructor of the React component is called before mounting. In the implementation of React Before adding other contents to the component constructor, you need to call super(props) to bind the props passed from the parent component to this class. Use this Props will get.

The official advice is not to introduce any code with side effects and subscription functions into the constructor, which should use componentDidMount().

Some initialization actions should be done in the constructor, such as initializing state and binding the event handler function to the class instance, but do not use setState(). If there is no need to initialize state or binding methods, there is no need to construct a constructor or replace this component with pure function writing.

Of course, you can also use props to initialize the state. Modifying the state later will not cause any modification to props, but you are still recommended to upgrade the state to the parent component or use redux for unified state management.

constructor(props) {
  super(props);
  this.state = {
    isLiked: props.isLiked
  };
}

(2) static getDerivedStateFromProps(nextProps, prevState)

In version 16.3 of React, the life cycle has been greatly adjusted in order for developers to correctly use the life cycle and avoid misunderstanding its concept and causing anti patterns.

This section will focus on the life cycle of getDerivedStateFromProps. It should be noted that the trigger range of getDerivedStateFromProps in React 16.3 is different from that in 16.4 ^. The main difference is whether it will be triggered during setState and forceUpdate. See this for details Life cycle diagram .

There are two possible usage scenarios:

  • Unconditionally update the internal state according to prop, that is, update the state as long as there is an incoming prop value
  • The state value is updated only when the prop value is different from the state value.

Let's look at a few examples.

Suppose we have a table component that updates the view based on the incoming list data.

class Table extends React.Component {
    state = {
        list: []
    }
    static getDerivedStateFromProps (props, state) {
        return {
            list: props.list
        }
    }
    render () {
        .... // Show list
    }
}

The above example is the first usage scenario, but the state is unconditionally updated from prop. We don't need to use this life cycle at all. Just operate on the prop value directly without saving the state value class.

Take another example. This example is a color selector. This component can select the corresponding color and display it. At the same time, it can display the color according to the incoming prop value.

Class ColorPicker extends React.Component {
    state = {
        color: '#000000'
    }
    static getDerivedStateFromProps (props, state) {
        if (props.color !== state.color) {
            return {
                color: props.color
            }
        }
        return null
    }
    ... // Select color method
    render () {
        .... // Display color and select color operation
    }
}

Now we can use this color selector to select colors, and we can pass in a color value and display it. However, there is a bug in this component. If we pass in a color value and then use the internal color selection method of the component, we will find that the color will not change. It is always the incoming color value.

This is a common bug that uses this lifecycle. Why did this bug happen? At the beginning, it was said that in the version of React 16.4 ^, setState and forceUpdate will also trigger this life cycle, so after the internal state changes, go to the getDerivedStateFromProps method and update the state value to the incoming prop.

Next, let's fix the bug.

Class ColorPicker extends React.Component {
    state = {
        color: '#000000',
        prevPropColor: ''
    }
    static getDerivedStateFromProps (props, state) {
        if (props.color !== state.prevPropColor) {
            return {
                color: props.color
                prevPropColor: props.color
            }
        }
        return null
    }
    ... // Select color method
    render () {
        .... // Display color and select color operation
    }
}

By saving a previous prop value, we can modify the state only when prop changes. This will solve the above problems.

Here is a summary of the points needing attention in using the getDerivedStateFromProps method:

  • When using this life cycle, pay attention to comparing the incoming prop value with the previous incoming prop.
  • Because this life cycle is a static method, while keeping it a pure function without side effects.

We should use the getDerivedStateFromProps lifecycle with caution. Pay attention to the following points when using:

  • Because this life cycle is a static method, while keeping it a pure function without side effects.
  • When using this life cycle, pay attention to comparing the incoming prop value with the previous incoming prop (this prop value should be unique, or use a unique prop value for special comparison).
  • Instead of using getDerivedStateFromProps, you can change the mode that the component remains completely uncontrollable. The scenario of prop changing state can be realized through the initial value and key value.

(3) componentWillMount() / UNSAFE_componentWillMount()

componentWillMount() will be deprecated in a future version of React (officially 17.0). UNSAFE_componentWillMount() is called before the component is mounted. Calling setState() in this method will not work because it is called before render().

To avoid side effects and other subscriptions, officials recommend using componentDidMount() instead. This method is the only method used on server rendering. Because this method is called before rendering, it is also the only place where you can directly modify the state synchronously.

(4) render()

The render() method is required. When he is called, he will calculate this Props and this State and returns one of the following types:

  • React element. Created by jsx, it can be either a dom element or a user-defined component.
  • String or number. They will be rendered into the dom as text nodes.
  • Portals. The new solution proposed in react 16 allows components to be detached from the parent component level and directly mounted anywhere in the DOM tree.
  • null, render nothing
  • Boolean value. It doesn't render anything.

When null and false are returned, reactdom Finddomnode (this) will return null and nothing will be rendered.

The render() method must be a pure function. It should not change the state or interact with the browser directly. It should put events in other life cycle functions.
If shouldComponentUpdate() returns false, render() will not be called.

(5) componentDidMount

componentDidMount is called immediately after the component is assembled. Initialization makes the DOM node should go here.

ajax requests are usually made here

If you want to initialize a third-party DOM library, you can also initialize it here. Only here can we get the real dom

(6) componentWillReceiveProps()/UNSAFE_componentWillReceiveProps(nextProps)

The official recommendation is to use the getDerivedStateFromProps function instead of componentWillReceiveProps. When the component is mounted, it will be called after receiving a new props. If you need to update the state in response to changes in props, you can do this Props and nextProps are compared, and this is used in this method setState().

If the parent component will re render the component, this method will be called even if props has not changed.

React does not call this method when the component initializes props. Call this Setstate will not trigger either.

(7) shouldComponentUpdate(nextProps, nextState)

Call shouldComponentUpdate to let React know whether the output of the component is affected by state and props. By default, each state change will be re rendered, and this default behavior should be maintained in most cases.

shouldComponentUpdate is called before rendering a new props or state. The default is true. This method will not be called during initialization or forceUpdate(). Returning false does not prevent the subcomponent from re rendering when the state changes.

If shouldComponentUpdate() returns false, componentWillUpdate,render and componentDidUpdate will not be called.

Officials do not recommend deep queries or JSON in shouldComponentUpdate() Stringify(), which is very inefficient and impairs performance.

(8) UNSAFE_componentWillUpdate(nextProps, nextState)

Unsafe when rendering a new state or props_ Componentwillupdate will be called as an opportunity to prepare before the update occurs. This method is not called during initialization.

You cannot use this here Setstate() also cannot do operations that will trigger view updates. If you need to update state or props, call getDerivedStateFromProps.

(9) getSnapshotBeforeUpdate()

Called before the output after react render() is rendered to the DOM. It enables your components to capture current values (such as scroll positions) before they are potentially changed. Any value returned by this lifecycle will be passed as a parameter to componentDidUpdate().

(10) componentDidUpdate(prevProps, prevState, snapshot)

Call componentDidUpdate() immediately after the update occurs. This method is not used for initial rendering. Use this as an opportunity to manipulate the DOM when the component is updated. As long as you compare the current props with the previous props (for example, if the props has not changed, you may not need a network request), this is also a good place to make a network request.

If the component implements the getSnapshotBeforeUpdate() life cycle, the value it returns will be passed to componentDidUpdate() as the third "snapshot" parameter. Otherwise, this parameter is undefined.

(11) componentWillUnmount()

Called immediately before the component is unloaded and destroyed. Perform any necessary cleanup in this method, such as invalidating timers, canceling network requests, or cleaning up any listeners created in componentDidMount.

(12) componentDidCatch(error, info)

The error boundary is the React component, which can capture JavaScript errors anywhere in its sub component tree, record these errors and display the fallback UI instead of the collapsed component tree. Error bounds catch errors during rendering, in the lifecycle method, and in the constructor under the entire tree.

If a class component defines this lifecycle method, it becomes an error boundary. Calling setState() in it allows you to catch unhandled JavaScript errors in the tree below and display a fallback UI. Only error boundaries can be used to recover from unexpected exceptions; Don't try to use them to control the process.

The error boundary captures only the errors in the following components in the tree. The error boundary itself cannot catch errors.

6,PureComponent

In purecompose, if the received new attribute or changed state is the same as the original attribute and original state, it will not be re render ed
shouldComponentUpdate can also be used in it, and. Whether to re render depends on the return value of shouldComponentUpdate.

import React, { PureComponent } from 'react'

class YourComponent extends PureComponent {
  ......
}

7,ref

The ref attribute provided by React is expressed as a reference to the real instance of the component, which is actually reactdom For the component instance returned by render (), ref can be attached to the component or dom element.

  • A ref attached to a component (a component declared by class) represents a reference to a component instance. The ref attribute cannot be used on functional components because they have no instances:
  • When mounted on a dom element, it represents a specific dom element node.

In the latest version of React, to use ref, you need to use React The createref method becomes a Ref.

import React, { Component, createRef } from 'react'
import ReactDOM from 'react-dom'

class App extends Component {
  constructor() {
    super()
    // Create inputRef
    this.inputRef = createRef()
  }
  componentDidMount () {
    console.log(this.inputRef.current) // <input type="text">
  }
  render () {
    return (
  		<div>
        {/* Associate ref and dom */}
        <input type="text" ref={this.inputRef} />
      </div>
  	)
  }
}
ReactDOM.render(
	<App/>,
  document.getElementById('root')
)

12, Component communication

Communication between parent and child components

  • The parent component passes its state to the child component, and the child component receives it as an attribute. When the parent component changes its state, the attribute received by the child component will change

  • The parent component marks the child component with ref, changes the state of the child component by calling the method of the child component, or calls the method of the child component

The child component communicates with the parent component

  • The parent component passes its own method to the child component, in which it can do any operation, such as changing the state. The child component passes this Props is called after receiving the method of the parent component.

Cross communication component

In react, there is no event bus similar to vue to solve this problem. We can only use their common parent-child components to implement it, replacing non parent-child relationship with multi-dimensional parent-child relationship. React provides a context api to realize cross component communication. The context api after React 16.3 is easier to use than the previous one.

Example, use context to realize the addition and subtraction function in the shopping cart

// counterContext.js
import React, { Component, createContext } from 'react'

const {
  Provider,
  Consumer: CountConsumer
} = createContext()

class CountProvider extends Component {
  constructor () {
    super()
    this.state = {
      count: 1
    }
  }
  increaseCount = () => {
    this.setState({
      count: this.state.count + 1
    })
  }
  decreaseCount = () => {
    this.setState({
      count: this.state.count - 1
    })
  }
  render() {
    return (
      <Provider value={{
        count: this.state.count,
        increaseCount: this.increaseCount,
        decreaseCount: this.decreaseCount
      }}
      >
        {this.props.children}
      </Provider>
    )
  }
}

export {
  CountProvider,
  CountConsumer
}
// Define CountButton component
const CountButton = (props) => {
  return (
    <CountConsumer>
      // The children of the consumer must be a method
      {
        ({ increaseCount, decreaseCount }) => {
          const { type } = props
          const handleClick = type === 'increase' ? increaseCount : decreaseCount
          const btnText = type === 'increase' ? '+' : '-'
          return <button onClick={handleClick}>{btnText}</button>
        }
      }
    </CountConsumer>
  )
}
// Define the count component to display the quantity
const Count = (prop) => {
  return (
    <CountConsumer>
      {
        ({ count }) => {
          return <span>{count}</span>
        }
      }
    </CountConsumer>
  )
}
// combination
class App extends Component {
  render () {
    return (
  		<CountProvider>
        <CountButton type='decrease' />
        <Count />
        <CountButton type='increase' />
      </CountProvider>
  	)
  }
}

Complex non parent-child component communication is difficult to deal with in react, and data sharing among multiple components is also difficult to deal with. In practical work, we will use flux, redux and mobx to realize it

13, Hoc (high order component)

Higher order components is a function that passes a component to it and returns a new component.

const NewComponent = higherOrderComponent(YourComponent)

For example, we want our component to automatically inject a copyright information.

// withCopyright.js defines a high-level component
import React, { Component, Fragment } from 'react'

const withCopyright = (WrappedComponent) => {
  return class NewComponent extends Component {
    render() {
      return (
        <Fragment>
          <WrappedComponent />
          <div>&copy;Copyright Qianfeng education 2019 </div>
        </Fragment>
      )
    }
  }
}
export default withCopyright
// Mode of use
import withCopyright from './withCopyright'

class App extends Component {
  render () {
    return (
  		<div>
        <h1>Awesome React</h1>
        <p>React.js Is a library for building user interfaces</p>
      </div>
  	)
  }
}
const CopyrightApp = withCopyright(App)

In this way, as long as we have components that need copyright information, we can directly use withCopyright, a high-level component package.

Here we will explain the support of configuring decorator mode in CRA.

14, Portal

Portals provides the best way to render components at DOM nodes outside the DOM structure level contained by the parent component.

ReactDOM.createPortal(child,container);

The first parameter child is the renderable react sub item, such as element, string or fragment. The second parameter, container, is a DOM element.

1. Usage

For ordinary components, the elements of child components will be mounted in the DOM node of the parent component.

render() {
  // React mounts a div node and renders child elements in the node
  return (
    <div>
      {this.props.children}
    </div>
  );
}

Sometimes you need to render elements to different positions in the DOM, which is the portal method used.

render(){
    // Instead of creating a div node, React renders the child elements onto the DOM node. domNode is a valid DOM node at any location.
    return ReactDOM.createPortal(
        this.props.children,
        domNode
    )
}

A typical usage is when the dom element of the parent component has the style of overflow:hidden or z-inde, and you need to display more child elements than the box of the parent element. For example, dialog boxes, hover boxes, and tips.

2. Event bubbling in protocol

Although the elements rendered through the portal are outside the box of the parent component, the rendered dom node is still on the element tree of React, and the click events on that dom element can still be heard in the dom tree.

import React, { Component } from 'react';
import ReactDOM from 'react-dom';

const getDiv = () => {
    const div = document.createElement('div');
    document.body.appendChild(div);
    return div;
};

const withPortal = (WrappedComponent) => {
    class AddPortal extends Component {
        constructor(props) {
            super(props);
            this.el = getDiv();
        }

        componentWillUnmount() {
            document.body.removeChild(this.el);
        }

        render(props) {
            return ReactDOM.createPortal(<WrappedComponent {...props} />, this.el);
        }
    }
    return AddPortal;
};

class Modal extends Component {
    render() {
        return (
            <div>
                <div>amodal content</div>
                <button type="button">Click</button>
            </div>
        );
    }
}

const PortalModal = withPortal(Modal);

class Page extends Component {
    constructor(props) {
        super(props);
        this.state = { clicks: 0 };
        this.handleClick = this.handleClick.bind(this);
    }

    handleClick() {
        this.setState(state => ({
            clicks: state.clicks + 1
        }));
    }


    render() {
        return (
            <div onClick={this.handleClick}>
                <h3>ppppppppp</h3>
                <h3>num: {this.state.clicks}</h3>
                <PortalModal />
            </div>
        );
    }
}

export default Page;

15, State management

1. Defects of traditional MVC framework

What is MVC?

[the external link image transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-N6x2u2aX-1606638421452)(./images/mvc-base.png)]

The full name of MVC is Model View Controller, which is the abbreviation of Model View Controller. It is a model of software design.

V, View, refers to the interface that users see and interact with.

M is the Model. The Model is the management data, and many business logics are completed in the Model. Among the three components of MVC, the Model has the most processing tasks.

C is the Controller, which means that the Controller accepts the user's input and calls the model and view to complete the user's requirements. The Controller itself does not output anything and do any processing. It just receives the request and decides which model component to call to process the request, and then determines which view to use to display the returned data.

MVC just looks beautiful

The data flow of MVC framework is ideal. The request comes to the Controller first, and the Controller calls the data in the Model to the View for rendering. However, in the actual project, the Model and View are allowed to communicate directly. Then there is the result:

[the external chain picture transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the picture and upload it directly (img-ttjxskfm-1606638421454) (. / images / defect of MVC. PNG)]

2,Flux

In 2013, Facebook unveiled react with the launch of the Flux framework. React is actually intended to replace jQuery, and Flux can actually be used to replace backbone js,Ember.js and a series of front-end JS frameworks of MVC architecture.

In fact, the application of Flux in React is similar to the role of Vuex in Vue, but in Vue, Vue is a complete mvvm framework, and Vuex is only a global plug-in.

React is just a V (view layer) in MVC. It only cares about the rendering in the page. Once there is data management, the ability of react itself is not enough to support projects with complex component structure. In traditional MVC, Model and Controller are needed. Facebook was not satisfied with the MVC framework in the world at that time, so it had Flux, but Flux was not an MVC framework, it was a new idea.

[the external chain picture transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the picture and upload it directly (img-A2oyXTq6-1606638421455)(./images/flux.png)]

  • View: view layer
  • ActionCreator: message sent by the view layer (such as mouseClick)
  • Dispatcher: used to receive Actions and execute callback functions
  • Store (data layer): used to store the status of the application. Once it changes, it will remind Views to update the page

Flux process:

  1. The component obtains the data saved in the store and mounts it in its own state
  2. The user generates an action and calls the actions method
  3. actions receives the user's operation and performs a series of logical code and asynchronous operations
  4. Then, actions will create a corresponding action with an identifying attribute
  5. actions calls the dispatcher's dispatch method to pass action s to the dispatcher
  6. After receiving the action and judging according to the identification information, the dispatcher calls the method of changing data in the store
  7. After the method of store is called, it changes the state and triggers one of its own events
  8. After the state of the store is changed, the event is triggered, and the handler of the event will notify the view to obtain the latest data

3,Redux

React is just an abstract layer of DOM, not a complete solution for Web applications. There are two aspects that it does not involve.

  • Code structure
  • Communication between components

In 2013, Facebook proposed the idea of Flux architecture, which triggered many implementations. In 2015, Redux appeared, combining Flux with Functional programming Together, it has become the most popular front-end architecture in a short time.

If you don't know if you need Redux, you don't need it

Only when you encounter problems that React can't solve, you need Redux

In short, if your UI layer is very simple and there is not much interaction, Redux is unnecessary. Using it will increase the complexity.

  • The user's usage is very simple
  • There is no collaboration between users
  • There is no need to interact with the server and WebSocket is not used
  • The View layer only gets data from a single source

Projects requiring Redux:

  • The usage of users is complex
  • Users with different identities have different usage methods (such as ordinary users and administrators)
  • Multiple users can collaborate
  • Interact with the server a lot, or use WebSocket
  • View wants to get data from multiple sources

From the component level, what kind of Redux is needed:

  • The state of a component needs to be shared
  • A state needs to be available anywhere
  • A component needs to change the global state
  • One component needs to change the state of another component

Redux's design idea:

  1. Web application is a state machine, and the view and state correspond to each other one by one.
  2. All States are saved in one object (unique data source).

Note: neither flux nor Redux must be used together with react, because flux and Redux are complete architectures. When learning react, only the components of react are used as the view layer in redux.

Three principles for the use of Redux:

  • Single source of truth
  • State is read-only
  • Changes are made with pure function-
  • Pure function: if the same input parameter, it must be the same return value
  • Input parameters cannot be changed

(1) Implement Redux yourself

In this part, instead of using react, you can directly use native html/js to write a simple redux

Basic state management and data rendering:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Redux principle 01</title>
</head>
<body>
  <h1>redux principle</h1>
  <div class="counter">
    <span class="btn" onclick="dispatch({type: 'COUNT_DECREMENT', number: 10})">-</span>
    <span class="count" id="count"></span>
    <span class="btn" id="add" onclick="dispatch({type: 'COUNT_INCREMENT', number: 10})">+</span>
  </div>
  <script>
    // Define the status of a counter
    const countState = {
      count: 10
    }

    // Set a method called changeState, which is used to process the data of state and return a new state every time
    const changeState = (action) => {
      switch(action.type) {
        // Processing minus
        case 'COUNT_DECREMENT':
          countState.count -= action.number
          break;
        // Processing plus        
        case 'COUNT_INCREMENT':
          countState.count += action.number
          break;
        default:
          break;
      }
    }

    // Define a method to render the dom of the counter
    const renderCount = (state) => {
      const countDom = document.querySelector('#count')
      countDom.innerHTML = state.count
    }
  
    // First render data
    renderCount(countState)

    // Define a dispatch method, which will be called automatically after receiving the action
    const dispatch = (action) => {
      changeState(action)
      renderCount(countState)
    }

  </script>
</body>
</html>

Create createStore method

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Redux principle 02</title>
</head>
<body>
  <h1>redux principle</h1>
  <div class="counter">
    <span class="btn" onclick="store.dispatch({type: 'COUNT_DECREMENT', number: 10})">-</span>
    <span class="count" id="count"></span>
    <span class="btn" id="add" onclick="store.dispatch({type: 'COUNT_INCREMENT', number: 10})">+</span>
  </div>
  <script>
    // Define a method for centralized management of state and dispatch
    const createStore = (state, changeState) => {
      // getState is used to get the state
      const getState = () => state
      
      // Define a listener to manage some methods
      const listeners = []
      const subscribe = (listener) => listeners.push(listener)

       // Define a dispatch method to return the result after render execution every time an action is passed in
      const dispatch = (action) => {
        // Call changeState to process the data
        changeState(state, action)
        // Let all the methods in the listener run
        listeners.forEach(listener => listener())
      }
      return {
        getState,
        dispatch,
        subscribe
      }
    }
    // Define the status of a counter
    const countState = {
      count: 10
    }
    // Set a method called changeState, which is used to process the data of state and return a new state every time
    const changeState = (state, action) => {
      switch(action.type) {
        // Processing minus
        case 'COUNT_DECREMENT':
          state.count -= action.number
          break;
        // Processing plus        
        case 'COUNT_INCREMENT':
          state.count += action.number
          break;
        default:
          break;
      }
    }

    // Create a store
    const store = createStore(countState, changeState)
    // Define a method to render the dom of the counter
    const renderCount = () => {
      const countDom = document.querySelector('#count')
      countDom.innerHTML = store.getState().count
    }
    // First render data
    renderCount()
    // Listen. As long as there is a dispatch, this method will run automatically
    store.subscribe(renderCount)
  </script>
</body>
</html>

Make the changeState method a pure function

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Redux principle 03</title>
</head>
<body>
  <h1>redux principle</h1>
  <div class="counter">
    <span class="btn" onclick="store.dispatch({type: 'COUNT_DECREMENT', number: 10})">-</span>
    <span class="count" id="count"></span>
    <span class="btn" id="add" onclick="store.dispatch({type: 'COUNT_INCREMENT', number: 10})">+</span>
  </div>
  <script>
    // Define a method for centralized management of state and dispatch
    const createStore = (state, changeState) => {
      // getState is used to get the state
      const getState = () => state
      
      // Define a listener to manage some methods
      const listeners = []
      const subscribe = (listener) => listeners.push(listener)

      // Define a dispatch method to return the result after render execution every time an action is passed in
      const dispatch = (action) => {
        // Call changeState to process the data
        state = changeState(state, action)
        // Let all the methods in the listener run
        listeners.forEach(listener => listener())
      }
      return {
        getState,
        dispatch,
        subscribe
      }
    }
    // Define the status of a counter
    const countState = {
      count: 10
    }
    // Set a method called changeState, which is used to process the data of state and return a new state every time
    const changeState = (state, action) => {
      switch(action.type) {
        // Processing minus
        case 'COUNT_DECREMENT':
          return {
            ...state,
            count: state.count - action.number
          }
        // Processing plus        
        case 'COUNT_INCREMENT':
          return {
            ...state,
            count: state.count + action.number
          }
        default:
          return state
      }
    }

    // Create a store
    const store = createStore(countState, changeState)
    // Define a method to render the dom of the counter
    const renderCount = () => {
      const countDom = document.querySelector('#count')
      countDom.innerHTML = store.getState().count
    }
    // First render data
    renderCount()
    // Listen. As long as there is a dispatch, this method will run automatically
    store.subscribe(renderCount)
  </script>
</body>
</html>

Merge state and changestate (final version)

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Redux principle 04</title>
</head>
<body>
  <h1>redux principle</h1>
  <div class="counter">
    <span class="btn" onclick="store.dispatch({type: 'COUNT_DECREMENT', number: 10})">-</span>
    <span class="count" id="count"></span>
    <span class="btn" id="add" onclick="store.dispatch({type: 'COUNT_INCREMENT', number: 10})">+</span>
  </div>
  <script>
    // Define a method for centralized management of state and dispatch. The name of changestate has been changed, and the professional name is reducer
    const createStore = (reducer) => {
      // Define an initial state
      let state = null
      // getState is used to get the state
      const getState = () => state
      
      // Define a listener to manage some methods
      const listeners = []
      const subscribe = (listener) => listeners.push(listener)

      // Define a dispatch method to return the result after the reducer executes every time an action is passed in
      const dispatch = (action) => {
        // Call reducer to process the data
        state = reducer(state, action)
        // Let all the methods in the listener run
        listeners.forEach(listener => listener())
      }
      //  Initialize state
      dispatch({})
      return {
        getState,
        dispatch,
        subscribe
      }
    }
    // Define the status of a counter
    const countState = {
      count: 10
    }
    // Set a method called changeState, which is used to process the data of state and return a new state every time
    const changeState = (state, action) => {
      // If state is null, countState is returned
      if (!state) return countState
      switch(action.type) {
        // Processing minus
        case 'COUNT_DECREMENT':
          return {
            ...state,
            count: state.count - action.number
          }
        // Processing plus        
        case 'COUNT_INCREMENT':
          return {
            ...state,
            count: state.count + action.number
          }
        default:
          return state
      }
    }

    // Create a store
    const store = createStore(changeState)
    // Define a method to render the dom of the counter
    const renderCount = () => {
      const countDom = document.querySelector('#count')
      countDom.innerHTML = store.getState().count
    }
    // First render data
    renderCount()
    // Listen. As long as there is a dispatch, renderCount will run automatically
    store.subscribe(renderCount)
  </script>
</body>
</html>

(2) Using Redux framework

Redux process:

[the external link image transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-VjAsiF0A-1606638421457)(./images/redux.png)]

  • store creates the initial state through reducer

  • view through store Getstate () gets the state saved in the store and mounts it on its own state

  • The actions method of the user is called, and the actions method is generated

  • The actions method is called to create an action with symbolic information

  • actions calls store The dispatch method is sent to the reducer

  • After receiving the action and judging according to the identification information, the reducer returns a new state

  • When the state of the store is changed to a new state by reducer, the store The callback function in the subscribe method will execute. At this time, you can notify the view to retrieve the state

Reducer must be a pure function:

The most important feature of reducer function is that it is a pure function. In other words, as long as it is the same input, it must get the same output. Reducer is not only available in Redux. The first parameter of the previously learned array method reduce is a reducer

Pure function is the concept of functional programming, and the following constraints must be observed.

  • Parameters must not be overwritten

  • API of system I/O cannot be called

  • Cannot call date Now () or math Random () and other impure methods, because they will get different results every time

Since Reducer is a pure function, it can ensure that the same State and the same View will be obtained. But because of this, the State cannot be changed in the Reducer function, and a new object must be returned. Please refer to the following writing.

// A State is an object
function reducer(state = defaultState, action) {
  return Object.assign({}, state, { thingToChange });
  // perhaps
  return { ...state, ...newState };
}

// State is an array
function reducer(state = defaultState, action) {
  return [...state, newItem];
}

It's best to make the State object read-only. The only way to get a new State is to generate a new object. The advantage is that at any time, the State corresponding to a View is always an immutable object.

We can set the default state by passing in the second parameter in createStore, but this form is only suitable for when there is only one reducer.

Partition reducer:

Because there can only be one large state in an application, there will be a lot of code in the reducer, so you can use the combineReducers method to merge the separated reducers together

be careful:

  1. When separating reducers, the state maintained by each reducer should be different
  2. Via store The data obtained by getstate will also be divided according to reducers
  3. When dividing multiple reducers, the default state can only be created in reducers, because the purpose of dividing reducers is to enable each reducer to manage part of the state independently

At first, it is generally based on the example of counter to explain the basic use of redux.

For more concepts about action/reducer/store, please see Official website

Redux asynchronous

Generally, an action is only an object and cannot contain asynchronous operations, which leads to many logic for creating an action can only be written in the component, which is not easy to reuse due to the large amount of code. At the same time, it is difficult to test this part of the code, and the business logic of the component is not clear. After using the middleware, you can write an action asynchronously through actionCreator, so that the code will be split into actionCreator, The maintainability is greatly improved, which is convenient for testing and reuse. At the same time, actionCreator also integrates different action distribution mechanisms in asynchronous operation to reduce the amount of code in the coding process

Common asynchronous libraries:

  • Redux-thunk
  • Redux-saga
  • Redux-effects
  • Redux-side-effects
  • Redux-loop
  • Redux-observable
  • ...

Promise based asynchronous Library:

  • Redux-promise
  • Redux-promises
  • Redux-simple-promise
  • Redux-promise-middleware
  • ...

(3) Smart/Container Components and dumb / presentation components

Display components Container assembly
effect Describe how to present (skeleton, style) Describe how to run (data acquisition, status update)
Use Redux directly no yes
data sources props Listen to Redux state
Data modification Call callback function from props Distribute actions to Redux
Call mode Manual Usually generated by React Redux

(4) Using react Redux

You can manually connect react and redux with context first.

React Redux provides two core APIs:

  • Provider: provide store

  • Connect: used to connect container components and presentation components

    • Provider

      According to the principle of single store, it usually only appears at the top level of the whole application.

    • connect

      The syntax format is

      connect(mapStateToProps?, mapDispatchToProps?, mergeProps?, options?)(component)

      Generally speaking, only the first two are used. Their functions are:

      • Store The state of getstate () is transformed into props of the display component
      • Transform actionCreators into methods on presentation component props

Special emphasis is placed on:

The second parameter on the official website is mapDispatchToProps, which is actually actionCreators

As long as there is a Provider component in the upper layer and a store is provided, any component at the descendant level can be connected through the connect method to use the state in the store. If you just want to connect to actionCreators, you can pass the first parameter as null

16, React Router

The current version of React Router is 5, which was released on March 21, 2019, Funny official website link , it was supposed to release version 4.4, but it turned out to be 5. Starting from 4, the way of use is different from the idea of the previous version. The idea of the previous version is the traditional idea: the route should be rendered in one place. After Route 4 is the idea that everything is a component

The React Router contains four packages:

Package name Description
react-router React Router core api
react-router-dom The DOM binding of the React Router, running in the browser, does not require additional installation of the React Router
react-router-native React Native In the actual application, it will not be used.
react-router-config Configuration of static route

Mainly use react router dom

1. Mode of use

Under normal circumstances, directly according to Official website The demo of understands the use of routing. There are several points that need special emphasis:

  • exact property of Route component

The exact attribute identifies whether it is a strict match. If it is true, it means a strict match. If it is false, it means a normal match.

  • render attribute of Route component instead of component attribute

How to pass attributes to components when rendering components? You cannot directly add attributes to a component by using component. Therefore, the Route component of React Router provides another way to render components, which is often used for permission management at the page component level.

  • Parameter transfer and acquisition of routing

  • Switch component

Always render the first matching component

  • Process 404 and default page

  • Use of withRoute high-order components

  • Method of managing a project route

  • code spliting

  • The difference between HashRouter and BrowserRouter, and the difference between front-end routing and back-end routing. This should have been mentioned in Vue.

2. Basic principle of React Router

React Router and even most front-end routes depend on history.js , it is an independent third-party js library. It can be used to manage history in different browsers and environments, and has a unified API.

  • History of the old browser: the history information in different states is stored through hash, corresponding to createHashHistory, by detecting location For the change of hash value, use location Replace method to realize url jump. Register and listen to the hashChange event on the window object to monitor the change of route and realize the fallback of history.
  • High version browser: use the history in HTML5, corresponding to createBrowserHistory, and use the methods including pushState and replaceState to jump. Register and listen to the pop state event on the window object to monitor the change of route and realize the fallback of history.
  • node environment: store history records in memory, corresponding to createMemoryHistory. push and pop status directly in memory.

17, Immutable js

1. JavaScript data modification problem

Look at a piece of familiar code

const state = {
  str: 'Qianfeng Education',
  obj: {
    y: 1
  },
  arr: [1, 2, 3]
}
const newState = state

console.log(newState === state) // true

Because js objects and arrays are reference types. Therefore, the state of the new state actually points to the same memory address, so the result is that the new state and state are equal.

Try to modify the data

const state = {
  str: 'Qianfeng Education',
  obj: {
    y: 1
  },
  arr: [1, 2, 3]
}
const newState = state

newState.str = 'Qianfeng Education H5 college'

console.log(state.str, newState.str)

As you can see, the modification of new state will also cause the modification of state. To solve this problem, js provides another way to modify data. Before modifying a data, make a copy of the data, like this

const state = {
  str: 'Qianfeng Education',
  obj: {
    y: 1
  },
  arr: [1, 2, 3]
}
const newState = Object.assign({}, state)

newState.str = 'Qianfeng Education H5 college'

console.log(state.str, newState.str)

We can copy data in js in many ways, such as... Object assign, Object. Free, slice, concat, map, filter, reduce and other methods are used for replication, but these are shallow copies, that is, only the first layer of data is copied, and the deeper layer of data is still the same reference, such as:

const state = {
  str: 'Qianfeng Education',
  obj: {
    y: 1
  },
  arr: [1, 2, 3]
}
const newState = Object.assign({}, state)

newState.obj.y = 2
newState.arr.push(4)

console.log(state, newState)

It can be seen that when changing the deeper data of newState, the value of state will still be affected. If you want deep replication, you have to make recursive copies layer by layer, which is a complex problem. Although some third-party libraries have helped us, such as lodash's cloneDeep method. Deep copy is very performance consuming.

import { cloneDeep } from 'lodash'

const state = {
  str: 'Qianfeng Education',
  obj: {
    y: 1
  },
  arr: [1, 2, 3]
}
const newState = cloneDeep(state)

newState.obj.y = 2
newState.arr.push(4)

console.log(state, newState)

2. What is immutable data

Immutable Data is data that cannot be changed once created. Any modification, addition or deletion of immutable object will return a new immutable object. The principle of immutable implementation is Persistent Data Structure, that is, when using old data to create new data, it is necessary to ensure that the old data is available and unchanged at the same time. At the same time, in order to avoid the s performance loss caused by deepCopy copying all nodes, immutable uses Structural Sharing, that is, if a node in the object tree changes, only this node and its affected parent node will be modified, and other nodes will be shared.

[the external chain picture transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the picture and upload it directly (img-ye32ksfn-1606638421459) (. / images / structure sharing. PNG)]

3,immutable. Advantages and disadvantages of JS

advantage:

  • Reduce the complexity of mutable
  • Save memory
  • Historical traceability (time travel): time travel refers to that the values are retained all the time. If you want to go back to which step, just simply take out the data. Think about it. If there is an undo operation on the page now, the data before Undo is retained and only need to be taken out. This feature is particularly useful in redux or flux
  • Embrace functional programming: immutable is originally the concept of functional programming. The characteristic of pure functional programming is that as long as the input is consistent, the output must be consistent. Compared with object-oriented, it is more convenient to develop components and debug. Recommend an online free book of functional programming< JS functional programming guide >, this book can be recommended to students as extracurricular supplementary reading.

Disadvantages:

  • Need to relearn api
  • Increase in resource package size (about 5000 lines of source code)
  • Easy to be confused with native objects: because the api is different from native objects, it is easy to make mistakes if mixed.

4. Use immutable js

reference resources Official website Focus on the creation, update and comparison of immutable data. For the employment class, you can master the following knowledge points.

01-get-started

const { Map } = require('immutable')
const map1 = Map({
  a: 1,
  b: 2,
  c: 3
})

const map2 = map1.set('b', 50)
console.log(map1.get('b') + ' vs. ' + map2.get('b'))

*// 2 vs. 50*

02-case-for-immutability-1.js

const { Map } = require('immutable')
const map1 = Map({ a: 1, b: 2, c: 3})
const map2 = Map({ a: 1, b: 2, c: 3})

console.log(map1.equals(map2))
console.log(map1 == map2)
console.log(map1 === map2)

// true
// false
// false

02-case-for-immutability-2.js

const { Map } = require('immutable')
const map1 = Map({ a: 1, b: 2, c: 3})
const map2 = map1.set('b', 2)

console.log(map1.equals(map2))
console.log(map1 == map2)
console.log(map1 === map2)

// true
// true
// true

02-case-for-immutability-3.js

const { Map } = require('immutable')
const map = Map({ a: 1, b: 2, c: 3})
const mapCopy = map

console.log(mapCopy.equals(map))

// true

03-JavaScript-first-API-0.js

const { List } = require('immutable')

const list1 = List([1, 2])
const list2 = list1.push(3, 4, 5)
const list3 = list2.unshift(0)
const list4 = list1.concat(list2, list3)

console.log(list1.size === 2)
console.log(list2.size === 5)
console.log(list3.size === 6)
console.log(list4.size === 13)

// true
// true
// true
// true

03-JavaScript-first-API-1.js

const { Map } = require('immutable')
const alpha = Map({ a: 1, b: 2, c: 3, d: 4 })
const upperCase = alpha.map((v, k) => k.toUpperCase()).join()

console.log(upperCase)
// A,B,C,D

03-JavaScript-first-API-2.js

const { Map, List } = require('immutable');

const map1 = Map({ a: 1, b: 2, c: 3, d: 4 });
const map2 = Map({ c: 10, a: 20, t: 30 });

const obj = { d: 100, o: 200, g: 300 };
const map3 = map1.merge(map2, obj)

console.log(map3)

// Map { "a": 20, "b": 2, "c": 10, "d": 100, "t": 30, "o": 200, "g": 300 }

const list1 = List([ 1, 2, 3 ]);
const list2 = List([ 4, 5, 6 ]);

const array = [ 7, 8, 9 ];
const list3 = list1.concat(list2, array);

console.log(list3)

// List [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ]

03-JavaScript-first-API-3.js

const { Seq } = require('immutable')

const myObject = {a: 1, b: 2, c: 3}
const seq = Seq(myObject).map(v => v * v)

const seqToObject = seq.toObject()
console.log(seq, seqToObject)

// Seq { "a": 1, "b": 4, "c": 9 } { a: 1, b: 4, c: 9 }

03-JavaScript-first-API-4.js

const { fromJS } = require('immutable')

const obj = { 1: 'one' }
console.log(Object.keys(obj)) *// [ '1' ]*
console.log(obj['1'], obj[1]) *// one one*

const map = fromJS(obj)
console.log(map.get('1'), map.get(1)) *// one undefined*

03-JavaScript-first-API-5.js

const { Map, List } = require('immutable')

const deep = Map({ a: 1, b: 2, c: List([ 3, 4, 5 ]) })
console.log(deep.toObject())
console.log(deep.toArray())
console.log(deep.toJS())
console.log(JSON.stringify(deep))

// { a: 1, b: 2, c: List [ 3, 4, 5 ] }
// [ 1, 2, List [ 3, 4, 5 ] ]
// { a: 1, b: 2, c: [ 3, 4, 5 ] }
// {"a":1,"b":2,"c":[3,4,5]}

03-JavaScript-first-API-6.js

const { Map, List } = require('immutable')

const mapped = Map({ a: 1, b: 2, c: 3 })
console.log(mapped.map(x => x * x))
console.log(mapped.map(function (x) {
  return x * x
}))

// Map { "a": 1, "b": 4, "c": 9 }
// Map { "a": 1, "b": 4, "c": 9 }

const aList = List([ 1, 2, 3 ])
const anArray = [ 0, ...aList, 4, 5 ]
console.log(anArray)

// [ 0, 1, 2, 3, 4, 5 ]

04-nested-structures.js

const { fromJS } = require('immutable')

const nested = fromJS({ a: { b: { c: [ 3, 4, 5 ] } } })
console.log(nested)
// Map { "a": Map { "b": Map { "c": List [ 3, 4, 5 ] } } }

const nested2 = nested.mergeDeep({ a: { b: { d: 6 } } })
console.log(nested2)
// Map { "a": Map { "b": Map { "c": List [ 3, 4, 5 ], "d": 6 } } }*

console.log(nested2.getIn([ 'a', 'b', 'd' ]))
// 6

const nested3 = nested2.updateIn([ 'a', 'b', 'd' ], value => value + 1)
console.log(nested3)
// Map { "a": Map { "b": Map { "c": "List [ 3, 4, 5 ]1", "d": 7 } } }

// Both setIn and updateIn can modify the deep-seated Immutable object. setIn directly passes the value, and updateIn passes the callback function
const nested4 = nested3.updateIn([ 'a', 'b', 'c' ], list => list.push(6))
console.log(nested4)
// Map { "a": Map { "b": Map { "c": List [ 3, 4, 5, 6 ], "d": 7 } } }

const nested5 = nested4.setIn(['a', 'b', 'd'], 90)

console.log(nested5)
console.log(nested)
console.log(nested2)
console.log(nested3)
console.log(nested4)

// Map { "a": Map { "b": Map { "c": List [ 3, 4, 5 ] } } }
// Map { "a": Map { "b": Map { "c": List [ 3, 4, 5 ], "d": 6 } } }
// Map { "a": Map { "b": Map { "c": List [ 3, 4, 5 ], "d": 7 } } }
// Map { "a": Map { "b": Map { "c": List [ 3, 4, 5, 6 ], "d": 7 } } }

05-Equality-treats-Collections-as-Values-0.js

const { Map, is } = require('immutable')

const obj1 = { a: 1, b: 2, c: 3 };
const obj2 = { a: 1, b: 2, c: 3 };
console.log(obj1 !== obj2)
// true

const map1 = Map({ a: 1, b: 2, c: 3 });
const map2 = Map({ a: 1, b: 2, c: 3 });

console.log(map1 !== map2)
console.log(map1.equals(map2))
console.log(is(map1, map2))

// true
// true
// true

05-Equality-treats-Collections-as-Values-1.js

const { Map, Set } = require('immutable')

const map1 = Map({ a: 1, b: 2, c: 3 });
const map2 = Map({ a: 1, b: 2, c: 3 });
const set = Set().add(map1)

console.log(set.has(map2))

// true

05-Equality-treats-Collections-as-Values-2.js

const { Map } = require('immutable');

const originalMap = Map({ a: 1, b: 2, c: 3 });
const updatedMap = originalMap.set('b', 2);
console.log(updatedMap === originalMap)

*// true*

05-Equality-treats-Collections-as-Values-3.js

const { Map, is } = require('immutable');

const originalMap = Map({ a: 1, b: 2, c: 3 });
const updatedMap = originalMap.set('b', 1000);
console.log(updatedMap !== originalMap)
// true

const anotherUpdatedMap = originalMap.set('b', 1000);
console.log(anotherUpdatedMap !== updatedMap)
console.log(anotherUpdatedMap.equals(updatedMap))
console.log(is(anotherUpdatedMap, updatedMap))
// true
// true
// true

06-Batching-Mutations.js

const { List } = require('immutable');

const list1 = List([ 1, 2, 3 ]);
const list2 = list1.withMutations(function (list) {
  list.push(4).push(5).push(6);
});

console.log(list1.size === 3);
console.log(list2.size === 6);
// true
// true

let map2 = map1.withMutations((map) => {
   // logic
   map.setIn(['c', 'd'], 9)
   map.set('a', 1)
})

let map3 = map1.updateIn(['c', 'd'], (v) => {
   return 9
})

console.log(map1 === map3)

07-Lazy-Seq-0.js

const { Seq } = require('immutable');

const oddSquares = Seq([ 1, 2, 3, 4, 5, 6, 7, 8 ])
  .filter(x => {
		console.log('filter x:' + x)
		return x % 2 !== 0
  })
  .map(x => {
		console.log('map x:' + x)
		return x * x
  });

console.log(oddSquares.get(1))

// filter x:1
// filter x:2
// filter x:3
// map x:3

// 9

07-Lazy-Seq-1.js

const { Seq, Map } = require('immutable');

const map = Map({ a: 1, b: 2, c: 3 });
const lazySeq = Seq(map);
const newMap = lazySeq
  .flip()
  .map(v => v.toUpperCase())
  .flip();

console.log(newMap)

// Seq { A: 1, B: 1, C: 1 }

07-Lazy-Seq-2.js

const { Range } = require('immutable');

const aRange = Range(1, Infinity)
  .skip(1000)
  .map(n => -n)
  .filter(n => n % 2 === 0)
  .take(2)
  .reduce((r, n) => r * n, 1);
  
console.log(aRange)

// 1006008

5. Using immutable. In redux js

redux official website Recommended use redux-immutable Integrate redux and immutable. Several points for attention:

In redux, combineReducers are used to merge multiple reductions. The combineReducers provided by redux only support native js forms, so it is necessary to use combineReducers provided by redux immutable instead

// Use the combineReducers method provided by Redux immutable to replace the combineReducers in redux
import {combineReducers} from 'redux-immutable'
import reducerOne from './reducerOne'
import reducerTwo from './reducerTwo'
 
const rootReducer = combineReducers({
    reducerOne,
    reducerTwo
});
 
export default rootReducer;

The initialState in the reducer also needs to be initialized to immutable type, such as a counter reducer

import { Map } from 'immutable'

import ActionTypes from '../actions'

const initialState = Map({
  count: 0
})

export default (state = initialState, action) => {
  switch (action.type) {
    case ActionTypes.INCREAMENT:
      return state.set('count', state.get('count') + 1) // Use set or setIn to change the value, and get or getIn to get the value
    case ActionTypes.DECREAMENT:
      return state.set('count', state.get('count') - 1)
    default:
      return state
  }
}

state becomes immutable, and mapStateToProp of connect needs to be changed accordingly

const mapStateToProps = state => ({
  count: state.getIn(['counter', 'count']) // Never use the 'toJS' method in mapStateToProps because it always returns a new object
})

Immutable can be used in shouldComponentUpdate Is or instance Equals to compare the data.

18, Lazy and suspend

1,React.lazy definition

React. The lazy function allows you to deal with dynamically introduced components like rendering conventional components.

What do you mean? In fact, it is lazy loading. The principle is to use the es6 import() function. This import is not an import command. The Import command is used to import modules synchronously, while the import () function is used to import modules dynamically.

When Webpack parses this syntax, it will automatically start code splitting and split it into a file. When this file is used, this code will be loaded asynchronously.

(1) Why code segmentation

As your program gets bigger and bigger, the amount of code gets bigger and bigger. There are many functions piled up on a page. Maybe some functions may not be used, but they are also downloaded and loaded on the page, so there must be room for optimization. Just like the theory of lazy loading of pictures.

(2) import function

javascript

//import command
import { add } from './math';

console.log(add(16, 26));

//import function
import("./math").then(math => {
  console.log(math.add(16, 26));
});

The dynamic import() syntax is currently just an ECMAScript (JavaScript) proposal, not a formal syntax standard. It is expected to be officially accepted in the near future. http://es6.ruanyifeng.com/#docs/module#import

(3) import function example

The following is an example of import:

Create two new files in the test folder

Picture 1:

test. The HTML code is as follows:

<div id="root">
  Page has no content
</div>
<button id="btn">load js</button>

<script>
  document.getElementById('btn').onclick=function(){
    import('./test.js').then(d=>{
      d.test()
    })
  }
</script>

test.js code is as follows:

function test(){
  document.getElementById('root')
  root.innerHTML='The page has changed to have content'
}
export {test}

Picture 2

At this time, open the web service and let the page access in the way of HTTP, http://192.168.1.2:8080/test.html

We can see from the Network under the developer tool of chrome that only one page has been requested.

Picture 3

But when we click load js, you will find test js will be added to the code in a dynamic way, and the test function is executed at the same time, which changes the content of the page.

Picture 4

In react Lazy and the commonly used third-party package react loadable use this principle, and then package and split the code with webpack to achieve asynchronous loading. In this way, the speed of first screen rendering will be greatly improved.

Because react Lazy does not support server-side rendering, so react loadable is a good choice at this time.

2. How to use react lazy

The following example code uses the create react app scaffold:

//OtherComponent.js file content

import React from 'react'
const OtherComponent = ()=>{
  return (
    <div>
      I have loaded
    </div>
  )
}
export default OtherComponent

// App.js file content
import React from 'react';
import './App.css';
//Use react Lazy import OtherComponent component
const OtherComponent = React.lazy(() => import('./OtherComponent'));
function App() {
  return (
    <div className="App">
      <OtherComponent/>
    </div>
  );
}
export default App;

This is the simplest react The page will be wrong, but it will be wrong. This error message reminds us that after react uses lazy, there will be a loading gap period. React doesn't know what to display in this gap period, so we need to specify it. The next step is to use suspend.

Picture 5

(1) Suspense

If the module containing OtherComponent has not been loaded after the App rendering is completed, we can use the load indicator to gracefully degrade this component. Here we use the suspend component to solve the problem.

Change the App component here

import React, { Suspense, Component } from 'react';
import './App.css';
//Use react Lazy import OtherComponent component
const OtherComponent = React.lazy(() => import('./OtherComponent'));
export default class App extends Component {
  state = {
    visible: false
  }
  render() {
    return (
      <div className="App">
        <button onClick={() => {
          this.setState({ visible: true })
        }}>
          load OtherComponent assembly
        </button>
        <Suspense fallback={<div>Loading...</div>}>
          {
            this.state.visible
              ?
              <OtherComponent />
              :
              null
          }
        </Suspense>
      </div>
    )
  }
}

We specify the gap period and use Loading to display it on the interface. After the asynchronous Loading of OtherComponent components is completed, replace the contents of OtherComponent components on Loading.

Picture 6

Picture 7

To demonstrate, I set the chrome network to lower end mobile, otherwise I won't see loading appear.

As can be seen from the above picture, when you click load, the code ` ` will be inserted into the head of the page, and a get request will be issued. The page starts to display loading and go to request 2 chunk. JS file.

The content returned at the end of the request is the content of the OtherComponent component, but the file name and file content have been processed by webpack.

Note: when suspend is used, the fallback must exist and contain content, otherwise an error will be reported.

19, React Hooks

In the world of React, there are container components and UI components. Before the emergence of React Hooks, we can use functions and stateless components to display the UI. For container components, function components are powerless. We rely on class components to obtain data, process data, and pass parameters down to UI components for rendering. In my opinion, using React Hooks has the following advantages over the previous class components:

  1. The code is more readable. The original code logic of the same function is split into different life cycle functions, which is easy to make it difficult for developers to maintain and iterate. The function code can be aggregated through React Hooks to facilitate reading and maintenance
  2. The level of component tree becomes lighter. In the original code, we often use HOC/render props and other methods to reuse the state and enhanced functions of components, which undoubtedly increases the number of component tree layers and rendering. In React Hooks, these functions can be realized through powerful custom Hooks

React in V16 The new features of React Hooks are introduced in version 8. Although the community has no best practice on how to build complex applications based on React Hooks (at least I haven't), by reading a large number of articles on this in the community, I will use ten cases to help you understand and skillfully use most of the features of React Hooks.

1. useState save component state

In the class component, we use this State to save the component state and modify it to trigger the component to re render. For example, the following simple counter component explains how class components work:

import React from "react";
class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0,
      name: "alife"
    };
  }
  render() {
    const { count } = this.state;
    return (
      <div>
        Count: {count}
        <button onClick={() => this.setState({ count: count + 1 })}>+</button>
        <button onClick={() => this.setState({ count: count - 1 })}>-</button>
      </div>
    );
  }
}

A simple counter component is completed. In the function component, because there is no black magic of this, React helps us save the state of the component through useState.

import React, { useState } from "react";
function App() {
  const [obj, setObject] = useState({
    count: 0,
    name: "alife"
  });
  return (
    <div className="App">
      Count: {obj.count}
      <button onClick={() => setObject({ ...obj, count: obj.count + 1 })}>+</button>
      <button onClick={() => setObject({ ...obj, count: obj.count - 1 })}>-</button>
    </div>
  );
}

After passing in the useState parameter, it returns an array with default state and change state functions. Change the original state value by passing in a new state to the function. It is worth noting that useState does not help you deal with the state. Compared with the non overwriting update state of setState, the overwriting update state of useState requires developers to deal with the logic themselves. (code above)

It seems that after there is a useState, the function component can also have its own state, but this is not enough.

2. useEffect treatment side effects

Function components can save the state, but they still can't do anything about the side effects of asynchronous requests. Therefore, React provides useEffect to help developers deal with the side effects of function components. Before introducing the new API, let's take a look at how class components do it:

import React, { Component } from "react";
class App extends Component {
  state = {
    count: 1
  };
  componentDidMount() {
    const { count } = this.state;
    document.title = "componentDidMount" + count;
    this.timer = setInterval(() => {
      this.setState(({ count }) => ({
        count: count + 1
      }));
    }, 1000);
  }
  componentDidUpdate() {
    const { count } = this.state;
    document.title = "componentDidMount" + count;
  }
  componentWillUnmount() {
    document.title = "componentWillUnmount";
    clearInterval(this.timer);
  }
  render() {
    const { count } = this.state;
    return (
      <div>
        Count:{count}
        <button onClick={() => clearInterval(this.timer)}>clear</button>
      </div>
    );
  }
}

In the example, the component updates the component status every second, and every time the update is triggered, the document is triggered Update the title (side effect), and modify the document when the component is unloaded Title (similar to clear)

It can be seen from the example that developers of some duplicate functions need to write repeatedly in componentDidMount and componentDidUpdate, but it is completely different if useEffect is used.

import React, { useState, useEffect } from "react";
let timer = null;
function App() {
  const [count, setCount] = useState(0);
  useEffect(() => {
    document.title = "componentDidMount" + count;
  },[count]);

  useEffect(() => {
    timer = setInterval(() => {
      setCount(prevCount => prevCount + 1);
    }, 1000);
    // Be sure to pay attention to the following order:
    // Tell react to call after the next component re rendering and before the next setInterval above
    return () => {
      document.title = "componentWillUnmount";
      clearInterval(timer);
    };
  }, []);
  return (
    <div>
      Count: {count}
      <button onClick={() => clearInterval(timer)}>clear</button>
    </div>
  );
}

We used useEffect to rewrite the above example. The first parameter of useEffect receives a function and can be used to do some side effects, such as asynchronous requests and modifying external parameters. The second parameter is called dependencies. It is an array. If the value in the array changes, the function in the first parameter of useEffect will be triggered. The return value (if any) is called before the component is destroyed or the function is called.

  • 1. For example, in the first useEffect, it is understood that once the count value changes, modify the document Title Value;
  • 2. An empty array [] is passed in the second useEffect. In this case, it will be triggered only when the component is initialized or destroyed. It is used to replace componentDidMount and componentWillUnmount. Use with caution;
    1. Another situation is that the second parameter is not passed, that is, useEffect only receives the first function parameter, which means that it does not listen to any parameter changes. Each time the DOM is rendered, the functions in useEffect are executed.

Based on this powerful Hooks, we can simulate and encapsulate other life cycle functions, such as componentDidUpdate. The code is very simple

function useUpdate(fn) {
    // useRef creates a reference
    const mounting = useRef(true);
    useEffect(() => {
      if (mounting.current) {
        mounting.current = false;
      } else {
        fn();
      }
    });
}

Now we have useState to manage state, useEffect to deal with side effects and asynchronous logic. Learning these two tricks is enough to deal with the use scenarios of most class components.

3. useContext reduce component level

The two most basic APIs, useState and useEffect, are introduced above. The useContext described below is encapsulated by React for you to handle the way of multi-level data transmission. In the past, when cross level ancestor components wanted to transmit data to grandchildren components, in addition to layer by layer props transmission, we can also use React Context API to help us do this. For example:

const { Provider, Consumer } = React.createContext(null);
function Bar() {
  return <Consumer>{color => <div>{color}</div>}</Consumer>;
}
function Foo() {
  return <Bar />;
}
function App() {
  return (
    <Provider value={"grey"}>
      <Foo />
    </Provider>
  );
}

Through the syntax of React createContext, data can be transferred to the Bar across the Foo component in the APP component. In React Hooks, we can use useContext to transform.

const colorContext = React.createContext("gray");
function Bar() {
  const color = useContext(colorContext);
  return <div>{color}</div>;
}
function Foo() {
  return <Bar />;
}
function App() {
  return (
    <colorContext.Provider value={"red"}>
      <Foo />
    </colorContext.Provider>
  );
}

context is passed to useContext instead of Consumer, and the return value is the data to be transmitted. The usage is very simple. Using useContext can solve the problem of multi state nesting of consumers.

function HeaderBar() {
  return (
    <CurrentUser.Consumer>
      {user =>
        <Notifications.Consumer>
          {notifications =>
            <header>
              Welcome back, {user.name}!
              You have {notifications.length} notifications.
            </header>
          }
      }
    </CurrentUser.Consumer>
  );
}

Using useContext becomes very concise, more readable, and does not increase the depth of the component tree.

function HeaderBar() {
  const user = useContext(CurrentUser);
  const notifications = useContext(Notifications);
  return (
    <header>
      Welcome back, {user.name}!
      You have {notifications.length} notifications.
    </header>
  );
}

4,useReducer

useReducer is almost the same as Redux/React-Redux in terms of use. The only thing missing is that the middleware provided by Redux cannot be used. We rewrite the timer component as useReducer,

import React, { useReducer } from "react";
const initialState = {
  count: 0
};
function reducer(state, action) {
  switch (action.type) {
    case "increment":
      return { count: state.count + action.payload };
    case "decrement":
      return { count: state.count - action.payload };
    default:
      throw new Error();
  }
}
function App() {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({ type: "increment", payload: 5 })}>
        +
      </button>
      <button onClick={() => dispatch({ type: "decrement", payload: 5 })}>
        -
      </button>
    </>
  );
}

The usage is basically the same as that of Redux, and the usage is also very simple. It can be regarded as providing a mini version of redux.

5. useCallback memory function

In class components, we often make the following mistakes:

class App {
    render() {
        return <div>
            <SomeComponent style={{ fontSize: 14 }} doSomething={ () => { console.log('do something'); }}  />
        </div>;
    }
}

What's the harm of writing like this? Once the props or state of the App component changes, it will trigger re rendering, even if it is not related to the SomeComponent component. Because each render will generate new style and doSomething (because style and doSomething point to different references before and after re rendering), it will lead to the re rendering of SomeComponent. If SomeComponent is a large component tree, Such a comparison of Virtual Dom is obviously wasteful, and the solution is also very simple. Extract the parameters into variables.

const fontSizeStyle = { fontSize: 14 };
class App {
    doSomething = () => {
        console.log('do something');
    }
    render() {
        return <div>
            <SomeComponent style={fontSizeStyle} doSomething={ this.doSomething }  />
        </div>;
    }
}

In class components, we can also store functions through this object, but we can't mount them in function components. Therefore, function components will re render sub components if there is a transfer function during each rendering.

function App() {
  const handleClick = () => {
    console.log('Click happened');
  }
  return <SomeComponent onClick={handleClick}>Click Me</SomeComponent>;
}

One more thing to say here is that the first version understands functional components as the syntax sugar of class component render function, so every time you re render, all the code inside the functional components will be re executed. Therefore, each render and handleClick in the above code will be a new reference, that is, the props passed to the SomeComponent component Onclick is always changing (because it is a new reference every time), so it is said that in this case, the function component will re render the sub component if there is a transfer function during each rendering.

It's different with useCallback. You can get a memorized function through useCallback.

function App() {
  const memoizedHandleClick = useCallback(() => {
    console.log('Click happened')
  }, []); // An empty array means that the function will not change under any circumstances
  return <SomeComponent onClick={memoizedHandleClick}>Click Me</SomeComponent>;
}

As the old rule, the second parameter is passed into an array. Once the value or reference of each item in the array changes, useCallback will return a new memory function for later rendering.

In this way, as long as the sub component inherits PureComponent or uses react Memo can effectively avoid unnecessary VDOM rendering.

6. useMemo memory assembly

The function of useCallback can be replaced by useMemo. If you want to return a memory function by using useMemo, it is also possible.

useCallback(fn, inputs) is equivalent to useMemo(() => fn, inputs).

Therefore, the previous example using useCallback can be rewritten using useMemo:

function App() {
  const memoizedHandleClick = useMemo(() => () => {
    console.log('Click happened')
  }, []); // An empty array means that the function will not change under any circumstances
  return <SomeComponent onClick={memoizedHandleClick}>Click Me</SomeComponent>;
}

The only difference is: * * useCallback will not execute the first parameter function, but will return it to you, while useMemo will execute the first function and return the function execution result to you** So in the previous example, you can return handleClick to store the function.

Therefore, useCallback often remembers event functions, generates memorized event functions and passes them to sub components for use. useMemo is more suitable for obtaining a certain value through function calculation, such as memory components.

function Parent({ a, b }) {
  // Only re-rendered if `a` changes:
  const child1 = useMemo(() => () => <Child1 a={a} />, [a]);
  // Only re-rendered if `b` changes:
  const child2 = useMemo(() => () => <Child2 b={b} />, [b]);
  return (
    <>
      {child1}
      {child2}
    </>
  )
}

When a/b changes, child1/child2 will re render. It can be seen from the example that the update of sub components will be triggered only when the value of the second parameter array changes.

7. useRef save reference value

useRef is similar to createRef and can be used to generate references to DOM objects. Take a simple example:

import React, { useState, useRef } from "react";
function App() {
  let [name, setName] = useState("Nate");
  let nameRef = useRef();
  const submitButton = () => {
    setName(nameRef.current.value);
  };
  return (
    <div className="App">
      <p>{name}</p>

      <div>
        <input ref={nameRef} type="text" />
        <button type="button" onClick={submitButton}>
          Submit
        </button>
      </div>
    </div>
  );
}

When the value returned by useRef is passed to the ref attribute of the component or DOM, you can access the component or the real DOM node through the ref.current value. The key point is that the component can also be accessed, so you can perform some operations on the DOM, such as listening to events.

Of course, useRef is far more powerful than you think. The function of useRef is a bit like class attributes, or you want to record some values in the component, and these values can be changed later.

Use useRef to bypass the feature of Capture Value. It can be considered that REF maintains a unique reference in all Render processes, so all assignments or values of ref get only one final state, and there will be no isolation between each Render.

Capture Value features exist in React Hooks:

function App() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    setTimeout(() => {
      alert("count: " + count);
    }, 3000);
  }, [count]);

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

Click the increase button first, then click the decrease button, and then alert 1 and then alert 0 after 3 seconds, instead of alert 0 twice. This is the so-called capture value feature. In the class component, the modified value is output after 3 seconds, because * * message is mounted on the this variable, which retains a reference value * *, and the access to the this attribute will obtain the latest value. At this point, you should understand that creating a reference with useRef can effectively avoid the capture value feature in React Hooks.

function App() {
  const count = useRef(0);

  const showCount = () => {
    alert("count: " + count.current);
  };

  const handleClick = number => {
    count.current = count.current + number;
    setTimeout(showCount, 3000);
  };

  return (
    <div>
      <p>You clicked {count.current} times</p>
      <button onClick={() => handleClick(1)}>increase count</button>
      <button onClick={() => handleClick(-1)}>reduce count</button>
    </div>
  );
}

As long as the object assigned and valued is changed into useRef instead of useState, you can avoid the capture value feature and get the latest value in 3 seconds.

8. useImperativeHandle transparent Ref

The useImperativeHandle is used to let the parent component get the index in the child component

import React, { useRef, useEffect, useImperativeHandle, forwardRef } from "react";
function ChildInputComponent(props, ref) {
  const inputRef = useRef(null);
  useImperativeHandle(ref, () => inputRef.current);
  return <input type="text" name="child input" ref={inputRef} />;
}
const ChildInput = forwardRef(ChildInputComponent);
function App() {
  const inputRef = useRef(null);
  useEffect(() => {
    inputRef.current.focus();
  }, []);
  return (
    <div>
      <ChildInput ref={inputRef} />
    </div>
  );
}

In this way, the App component can obtain the DOM node of the input of the sub component.

9. Side effects of uselayouteffect synchronous execution

In most cases, using useEffect can help us deal with the side effects of components, but if you want to synchronously call some side effects, such as the operation on DOM, you need to use useLayoutEffect. The side effects in useLayoutEffect will be executed synchronously after the DOM is updated.

function App() {
  const [width, setWidth] = useState(0);
  useLayoutEffect(() => {
    const title = document.querySelector("#title");
    const titleWidth = title.getBoundingClientRect().width;
    console.log("useLayoutEffect");
    if (width !== titleWidth) {
      setWidth(titleWidth);
    }
  });
  useEffect(() => {
    console.log("useEffect");
  });
  return (
    <div>
      <h1 id="title">hello</h1>
      <h2>{width}</h2>
    </div>
  );
}

In the above example, useLayoutEffect will trigger the function synchronously after the update of render and DOM, which is better than the asynchronous trigger function of useEffect.

(1) What's the difference between useeffect and useLayoutEffect?

To put it simply, the call timing is different. useLayoutEffect is consistent with the original componentdidmount & componentdidupdate. After the DOM update is completed by react, the code called will be synchronized immediately, which will block the page rendering. useEffect is the code that will not be called until the whole page is rendered.

Officials suggest giving priority to useEffect

However, we recommend starting with useEffect first and only trying useLayoutEffect if that causes a problem.

In actual use, if you want to avoid page jitter (it is possible to modify DOM in useEffect), you can put the code that needs to operate DOM in uselayouteeffect. About using useEffect to cause page jitter.

However, useLayoutEffect will have a warning when rendering on the server. To eliminate it, use useEffect instead or postpone the rendering time.

20, Mobx

Mobx is a powerful and easy to use state management tool. The author of Redux has also recommended it to you. In many cases, mobx can be used to replace redux.

[the external chain picture transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the picture and upload it directly (img-d7bmnqs4-1606638421466) (. / images / mobx flow. PNG)]

This picture comes from the official website. I understand it clearly. Basically, the understanding of mobx is the beginning.

The official website has clear core concepts and usage methods, and is equipped with egghead Video tutorial. I won't repeat them one by one here.

In particular, when using mobx react, you can define a new lifecycle hook function componentWillReact. When the component changes because of the data it observes, it will arrange to re render. At this time, componentWillReact will be triggered. This makes it easy to trace the rendering and find the action that caused the rendering.

  • componentWillReact does not receive parameters

  • componentWillReact will not trigger before initializing rendering (use componentWillMount instead)

  • componentWillReact for mobx-react@4+ , this hook will be triggered when a new props is received and after the setState call

  • To trigger componentWillReact, the observed variable must be used in render

  • componentWillReceiveProps will not be triggered after Mobx is used

1. Build environment

mkdir my-app
cd my-app
npm init -y
npm i webpack webpack-cli webpack-dev-server -D
npm i html-webpack-plugin -D
npm i babel-loader @babel/core @babel/preset-env -D
npm i @babel/plugin-proposal-decorators @babel/plugin-proposal-class-properties -D
npm i @babel/plugin-transform-runtime -D
npm i @babel/runtime -S
npm i mobx -S
mkdir src
mkdir dist
touch index.html
touch src/index.js
touch webpack.config.js

Write webpack config. js

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  mode: 'development',
  entry: path.resolve(__dirname, 'src/index.js'),
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'main.js'
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env'],
            plugins: [
              //Support decorator
              ["@babel/plugin-proposal-decorators", { "legacy": true }],
              ["@babel/plugin-proposal-class-properties", { "loose" : true }],
              ['@babel/plugin-transform-runtime']
            ]
          }
        }
      }
    ]
  },
  plugins: [new HtmlWebpackPlugin()],
  devtool: 'inline-source-map'
}

Write index html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>

</body>
</html>

2. Getting started with Mobx

(1) observable state

  • map
import {observable} from 'mobx'
// statement
const map = observable.map({a: 1, b: 2});
// set up
map.set('a', 11);
// obtain
console.log(map.get('a'));
console.log(map.get('b'));
// delete
map.delete('a');
console.log(map.get('a'));
// Determine whether there are attributes
console.log(map.has('a'));
  • object
import {observable} from 'mobx'
// statement
const obj = observable({a: 1, b: 2});
// modify
obj.a = 11;
// visit
console.log(obj.a, obj.b);
  • array
import {observable} from 'mobx'
const arr = observable(['a', 'b', 'c', 'd']);
// visit
console.log(arr[0], arr[10]);
// operation
arr.pop();
arr.push('e');
  • Foundation type
import {observable} from 'mobx'/
const num = observable.box(10);
const str = observable.box('hello');
const bool = observable.box(true);
// Get value
console.log(num.get(), str.get(), bool.get());
// Modify value
num.set(100);
str.set('hi');
bool.set(false);
console.log(num.get(), str.get(), bool.get());

(2) observable decorator

import {observable} from 'mobx'

// observable this function can identify whether it is a normal function call or a decorator call
// If it is a decorator, it will automatically identify the data type and use different packaging conversion schemes.
class Store{
  @observable arr = [];
  @observable obj = {a: 1};
  @observable map = new Map();
  @observable str = 'hello';
  @observable num = 123;
  @observable bool = false;
}

const store = new Store();

console.log(store);
console.log(store.obj.a);

Note: in the vscode compiler, js files will be red when using decorator. Solution:

Write jsconfig. In the root directory json

{
  "compilerOptions": {
    "module": "commonjs",
    "target": "es6",
    "experimentalDecorators": true
  },
  "include": ["src/**/*"]
}

(3) Respond to observables

  • Basic code:
import {observable} from 'mobx'
class Store{
  @observable arr = [];
  @observable obj = {a: 1};
  @observable map = new Map();
  @observable str = 'hello';
  @observable num = 123;
  @observable bool = false;
}
const store = new Store();
  • computed

The calculated value is a value that can be derived from the existing state or other calculated values, which is very similar to the calculated value in vue.

const result = computed(()=>store.str + store.num);
console.log(result.get());
// Monitor data changes
result.observe((change)=>{
  console.log('result:', change);
})
//Two modifications to the store attribute will cause the result to change
store.str = 'world';
store.num = 220;

computed can be used as a decorator to add the calculation of result to the class:

class Store{
  @observable arr = [];
  @observable obj = {a: 1};
  @observable map = new Map();

  @observable str = 'hello';
  @observable num = 123;
  @observable bool = false;

  @computed get result(){
    return this.str + this.num;
  }  
}
  • autorun

When you want to create a responsive function and the function itself will never have an observer, you can use mobx autorun

The provided function is always triggered immediately, and then triggered again every time its dependency changes.

Rule of thumb: if you have a function that should run automatically but will not produce a new value, use autorun. computed should be used in all other cases.

//aotu will trigger once immediately
autorun(()=>{
  console.log(store.str + store.num);
})

autorun(()=>{
  console.log(store.result);
})
//Both modifications will cause autorun to execute
store.num = 220;
store.str = 'world';
  • when
when(predicate: () => boolean, effect?: () => void, options?)

when observe and run the given predicate until it returns true. Once true is returned, the given effect is executed, and then autorunner is cleaned up. This function returns a cleaner to cancel the AutoRun ahead of time.

This function is useful for processing or cancelling in a responsive manner.

when(()=>store.bool, ()=>{
  console.log('when function run.....');
})
store.bool = true;
  • reaction

Usage: reaction (() = > data, (data, reaction) = > {sideeffect}, options?).

A variant of autorun gives finer grained control over how to track observable. It receives two function parameters. The first (data function) is used to track and return data as the input of the second function (effect function). Unlike autorun, the effect function will not run directly when it is created. It will run only after the data expression returns a new value for the first time. Any observable accessed when the effect function is executed is not tracked.

// reaction
reaction(()=>[store.str, store.num], (arr)=>{
  console.log(arr.join('/'));
})
//As long as any value in [store.str, store.num] changes, the second function of reaction will execute
store.num = 220;
store.str = 'world';

(4) Change observables status

  • action

Follow the above case and add action to the class:

class Store{
  @observable arr = [];
  @observable obj = {a: 1};
  @observable map = new Map();

  @observable str = 'hello';
  @observable num = 123;
  @observable bool = false;

  @computed get result(){
    return this.str + this.num;
  }

  @action bar(){
    this.str = 'world';
    this.num = 40;
  }
}
const store = new Store();

//When action is called, it will be executed only once
store.bar();
  • action.bound

action.bound can be used to automatically bind actions to target objects.

class Store{
  @observable arr = [];
  @observable obj = {a: 1};
  @observable map = new Map();

  @observable str = 'hello';
  @observable num = 123;
  @observable bool = false;

  @computed get result(){
    return this.str + this.num;
  }

  @action bar(){
    this.str = 'world';
    this.num = 40;
  }

  //this is always right
  @action.bound foo(){
    this.str = 'world';
    this.num = 40;
  }
}

const store = new Store();
setInterval(store.foo, 1000)
  • runInAction

action can only affect the running function, not the asynchronous operation of the current function call. If you use async function to handle business, we can use runInAction API to solve this problem.

@action async fzz() {
  await new Promise((resolve) => { 
    setTimeout(() => {
      resolve({
        num: 220,
        str: 'world'
      })
    }, 1000) 
  })
  runInAction(()=>{
    store.num = 220
    store.str = 'world'
  })    
}

3. Apply

(1) Using mobx in react

Using mobx in react requires the help of mobx react.

Its function is equivalent to using Redux in react, which needs the help of react redux.

First, build the environment:

create-react-app react-app
cd react-app
npm run eject
npm i @babel/plugin-proposal-decorators @babel/plugin-proposal-class-properties -D
npm i mobx mobx-react -S

Modify package Configuration of babel in JSON:

  "babel": {
    "presets": [
      "react-app"
    ],
    "plugins": [
      [
        "@babel/plugin-proposal-decorators",
        {
          "legacy": true
        }
      ],
      [
        "@babel/plugin-proposal-class-properties",
        {
          "loose": true
        }
      ]
    ]
  }

Note: in the vscode compiler, js files will be red when using decorator. Solution:

Write jsconfig. In the root directory json

{
  "compilerOptions": {
    "module": "commonjs",
    "target": "es6",
    "experimentalDecorators": true
  },
  "include": ["src/**/*"]
}

additional

1, Create react app supports decorators

yarn add @babel/core @babel/plugin-proposal-decorators @babel/preset-env

establish. babelrc

{
    "presets": [
        "@babel/preset-env"
    ],
    "plugins": [
        [
            "@babel/plugin-proposal-decorators",
            {
                "legacy": true
            }
        ]
    ]
}

Create config overrides js

const path = require('path')
const { override, addDecoratorsLegacy } = require('customize-cra')

function resolve(dir) {
    return path.join(__dirname, dir)
}

const customize = () => (config, env) => {
    config.resolve.alias['@'] = resolve('src')
    if (env === 'production') {
        config.externals = {
            'react': 'React',
            'react-dom': 'ReactDOM'
        }
    }

    return config
};


module.exports = override(addDecoratorsLegacy(), customize())

Installation dependency

yarn add customize-cra react-app-rewired

Modify package json

...
"scripts": {
    "start": "react-app-rewired start",
    "build": "react-app-rewired build",
    "test": "react-app-rewired test",
    "eject": "react-app-rewired eject"
  },
...

Tags: React

Posted by Sam on Thu, 05 May 2022 06:11:42 +0300