React

React is based on the following concepts:

  1. component
  2. JSX
  3. Virtual DOM
  4. Data Flow

React. Js is not a framework, it’s just a library. It only provides uI-level solutions. In the actual project, it can not solve all our problems, we need to combine other libraries, such as Redux, React-Router, etc., to help provide a complete solution.

React browser development environment

<! DOCTYPEhtml>
<html>
  <head>
    <meta charset="UTF-8" />
    <script src="https://unpkg.com/react@17/umd/react.development.js" crossorigin></script>
    <script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js" crossorigin></script>
    <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
  </head>
  <body>
    <div id="root"></div>
    <! Type ="text/ Babel "-->
    <script type="text/babel">
      Create a virtual DOM
      const vDom = <h1>Hello,React</h1>
      // 2. Render the virtual DOM to the page
      ReactDOM.render(vDom, document.getElementById('root'))
    </script>
  </body>
</html>
Copy the code

React, React -dom, and Babel are three libraries that must be loaded first.

  1. react.js:ReactCore library.
  2. react-dom.js: be responsible forWebOf the pageDOMOperation.
  3. babel.jsWill:JSXGrammar toJavaScriptSyntax.

Introduction to Virtual DOM

A real page corresponds to a DOM tree. In the traditional page development model, each time a page needs to be updated, you manually manipulate the DOM to do so. DOM manipulation is very expensive. And the code that manipulates the DOM becomes unmaintainable.

React converts a real DOM tree into a JavaScript object tree, also known as a Virtual DOM. The diagram below:

After each data update, the Virtual DOM is recalculated and compared with the Virtual DOM generated last time, and the changed part is updated in batches. VirtualDOM not only improves React performance, but its biggest benefit is that it can be integrated on other platforms (for example, React-Native is a native control rendered based on the VirtualDOM).

Therefore, when the Virtual DOM is output, whether to output the Web DOM, Android control or iOS control depends on the platform.

Iv. Introduction to JSX

Hello,React

 <body>
  <div id="root"></div>
  <script type="text/babel">
    Create a virtual DOM
    const element = React.createElement('h1', { id: 'title' }, React.createElement('span'.null.'Hello,React! '))
    // 2. Render the virtual DOM to the page
    ReactDOM.render(element, document.getElementById('root'))
  </script>
</body>
Copy the code

React.createElement builds a JavaScript object that describes HTML structure information, including tag names, attributes, and child elements. An object structure similar to the following:

{
    type: 'h1'.props: {
      id: 'title'.children: {
        type: 'span'.props: {
          children: 'Hello,React'}}}}Copy the code

Creating the virtual DOM with react. createElement is difficult enough when the tag structure is not too complex. It doesn’t have the brevity of writing structures in HTML. To solve this problem, the JSX syntax was born. If we were to reexpress the above elements using JSX syntax, we would simply write:

<body>
  <div id="root"></div>
  <script type="text/babel">
    Create a virtual DOM
    const element = (
      <h1 id="title">
        <span>Hello,React</span>
      </h1>
    )
    // 2. Render the virtual DOM to the page
    ReactDOM.render(element, document.getElementById('root'))
  </script>
</body>
Copy the code

JSX adds HTML syntax directly to JavaScript code, making it more intuitive and easier to maintain. Converted to pure JavaScript by the compiler and executed by the browser. JSX has been compiled into pure JavaScript at the product packaging stage.

JSX is a syntactic extension of the JavaScript language that looks like HTML but is not HTML. JSX is a third-party standard that can be applied to any set of frameworks. Compiling JSX syntax is possible using Babel’s JSX compiler.

4.1. HTML attributes in JSX

React differs from HTML in a number of attributes. For example, class is rewritten as className:

 ReactDOM.render(<div className="foo">Hello</div>.document.getElementById('root'))
Copy the code

4.2. JavaScript expressions in JSX

When JSX encounters an HTML tag (beginning with <), it parses it with HTML rules. When you encounter a block of code (beginning with {), you parse it with JavaScript rules. That is, JSX uses expressions wrapped in curly braces {} :

<body>
  <div id="root"></div>
  <script type="text/babel">
    const names = ['zhangsan'.'lisi'.'wangwu']
    const title = 'title'
    ReactDOM.render(
      <div>
        <h1>{title}</h1>
        <div>
          {names.map((name, key) => {
            return <div key={key}>Hello,{name}</div>
          })}
        </div>
      </div>.document.getElementById('root'))</script>
</body>
Copy the code

JSX can insert JavaScript variables directly into templates. If the variable is an array, all members of the array are expanded.

const names = [<div key="1">zhangsan</div>.<div key="2">lisi</div>]

ReactDOM.render(
  <div>
    {names}
  </div>.document.getElementById('root'))Copy the code

Any expression can be embedded in {} of JSX. {{}} returns an object with an object literal inside {}. For example, inline styles should be written in the form of {{key:value}} :

<span style={{ color: 'red' }}>Hello,React</span>
Copy the code

4.3 comments in JSX

Using comments in JSX is also very simple, just using JavaScript, the only thing to note is that using comments in the child element of a component is wrapped in {}.

const element = (
/* Multi-line comments */

// Single-line comment
<h1 id="title">{/* Node comments */}<span>Hello,React</span>
</h1>
)

ReactDOM.render(element, document.getElementById('root'))
Copy the code

4.4 HTML escape in JSX

React escapes all strings to be displayed in the DOM to prevent XSS. So any HTML format will be escaped:

class MyComponent extends React.Component {
  constructor(props) {
    super(props)
    this.state = { title: '< h1 > title < / h1 >'}}render() {
    return <div>{this.state.title}</div>
  }
}

ReactDOM.render(<MyComponent />.document.getElementById('root'))
Copy the code

Expression inserts do not render a

to the page, but render it as text:

AngerouslySetInnerHTML React provides an alternative to the innerHTML provided by the browser DOM. DangerouslySetInnerHTML passes in an object, The __html attribute value of this object is equivalent to innerHTML of the element:

render() {
  return <div dangerouslySetInnerHTML={{ __html: this.state.title}} ></div>
}
Copy the code

4.5. Custom HTML attributes in JSX

If an attribute used in JSX does not exist in the HTML specification, it is ignored. If you want to use custom attributes, you can use the data- prefix. The accessibility attribute prefix aria- is also supported.

 ReactDOM.render(<div data-attr="abc">content</div>.document.getElementById('root'))
Copy the code

4.6. Fragment Labels

React.Fragment represents an empty tag. It allows multiple elements to be returned in the Render () method without creating additional DOM elements. Fragment can only accept key attributes:

import React from 'react'

class App extends React.Component {
  render() {
    return (
      <React.Fragment key={1}>
        <p>App</p>
      </React.Fragment>)}}Copy the code

Fragment the Fragment syntax is <>, but the Fragment cannot use key:

<>
  <p>App</p>
</>
Copy the code

Five, componentization

React allows code to be wrapped as components. Component can be a class Component or a function Component.

Components have three core concepts. The React component is basically composed of components’ external property states (PROPS), internal property states (States), and lifecycle methods. The diagram below:

The HTML structure generated by the component can only have a single root node. In React, data flows one way from top to bottom, from parent to child components.

There are two different methods for building React components: ES6 class and stateless function.

Use of React. CreateClass is not recommended after [email protected].

5.1. Write components in class mode

class Hello extends React.Component {
  constructor(props) {
    super(props)
  }
  render() {
    return (<h1>Hello,React</h1>)
  }
}
ReactDOM.render(<Hello />.document.getElementById('root'))
Copy the code

5.2. Stateless Components

A pure function can be used to define a stateless function, which has no state and no life cycle and simply receives props rendering to generate a DOM structure. Stateless components are very simple and have low overhead. For example, using a function definition:

function Person(props) {
  return <h1>Hello, {props.name}</h1>
}
ReactDOM.render(<Person name="Zhang" />.document.getElementById('root'))
Copy the code

Sixth, the state

State is the current state (data) of the component. Once the state (data) changes, the component automatically calls Render to rerender the UI, which is triggered by the this.setState method.

Instead of modifying state directly, use the setState() method. If you change state directly, the component does not refire the Render method:

// Error, will not trigger render() to rerender
this.state.count = this.state.count + 1
Copy the code

6.1 introduction to setState() method

The setState() method, which takes an object or function as an argument to update state.

  • Object as a parameter is passed in only the parts that need to be updated,ReactShallow merge is automatically performed without passing in the entire object:
class MyComponent extends React.Component {
  constructor(props) {
    super(props)
    this.state = { count: 0.name: 'Joe'}}handleClick() {
    // Change only count
    this.setState({ count: this.state.count + 1})}render() {
    return <div onClick={this.handleClick.bind(this)}>{this.state.count}</div>
  }
}
ReactDOM.render(<MyComponent />.document.getElementById('root'))
Copy the code
  • Function as an argumentprevStateandpropsTwo parameters,stateThe value of is returned by the object:
  1. prevStateA:state.
  2. props: Updates when appliedprops.
class MyComponent extends React.Component {
  constructor(props) {
    super(props)
    this.state = { count: 0}}handleClick(e) {
    this.setState((prevState, props) = > {
      // return first state
      return { count: this.state.count + 1}})this.setState((prevState, props) = > {
      // The first state is the state of the previous one
      console.log(prevState) / / 1
      return { count: this.state.count + 1}})}render() {
    return <div onClick={this.handleClick.bind(this)}>{this.state.count}</div>
  }
}
ReactDOM.render(<MyComponent />.document.getElementById('root'))
Copy the code

6.2 setState() asynchronous update

The setState() method updates state asynchronously. React doesn’t change state right away. Instead, the object is placed on an update queue, and the new state is extracted from the queue and merged into state, which triggers the component update:

class MyComponent extends React.Component {
  constructor(props) {
    super(props)
    this.state = { count: 0}}handleClick(e) {
    this.setState({ count: this.state.count + 1 })
    console.log(this.state.count) / / 0
  }
  render() {
    return <div onClick={this.handleClick.bind(this)}>{this.state.count}</div>
  }
}
ReactDOM.render(<MyComponent />.document.getElementById('root'))
Copy the code

When the handleClick event is triggered, the this.state.count output is 0.

One solution is that the setState(updater, [callback]) method can also pass in a callback function that will be called once setState() is done and the component is redrawn.

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

6.3 setState() shallow merge

React.js may merge multiple setState() state changes into a single state change for performance reasons. So don’t rely on the current setState() to calculate the next State. A counter like this:

class AddCount extends React.Component {
  constructor(props) {
    super(props)
    this.state = { count: 0}}handleClick() {
    this.setState({ count: this.state.count + 1 })
    this.setState({ count: this.state.count + 1})}render() {
    return (
      <div>{/* count outputs 1 */}<h2>{this.state.count}</h2>
        <button onClick={this.handleClick.bind(this)}>add</button>
      </div>)}}Copy the code

When the handleClick event is triggered, the page outputs 1. Although the setState() method is called twice. This is because when setState() is called to modify the component state, the update of the component state is actually a shallow merge, equivalent to:

Object.assign(
  previousState,
  {count: state.count + 1},
  {count: state.count + 1},...).Copy the code

So use a function as the setState() argument if subsequent operations depend on the result of the previous setState(). React will pass the result of the last setState() to this function, which can operate on the last correct result, and return an object to update the state:

handleClick () {
  this.setState({ count: this.state.count + 1 })
  this.setState((prevState) = > {
    / / 1
    console.log(prevState.count)
    return { count: prevState.count + 1}}}})Copy the code

Pass the value updated by the last setState() operation to the next setState() to display count correctly.

6.4, State is Immutable

React officially recommends treating state as an Immutable object. All states contained in state should be Immutable. When a state in state changes, we should recreate the state object instead of directly modifying the original state.

When adding a name to name, use the concat method of the array or the ES6 extension operator:

const names = ['Joe']
/ / 1
this.setState(prevState= > ({
  names: prevState.names.concat(['bill'])}))/ / 2
this.setState(prevState= > ({
  names: [...prevState.names,'bill']}))Copy the code

Do not use methods like push, POP, Shift, unshift, splice, etc., to modify the state of an array type, because these methods modify the original array, while concat, slice, etc., return a new array.

If we have a state of the Object type person, to not change the original Object, we can use object. assign or extend the Object property:

const person = { age: 30 }

/ / 1
function updatePerson (person) {
  return Object.assign({}, person, { age: 20})}/ / 2
function updatePerson (person) {
  return{... person,age:20}}Copy the code

To create a new state object, avoid methods that directly modify the original object, and instead use methods that return a new object.

6.5. Modify deeply nested objects

Because setState() merges only the first level of an object’s attributes. If you want to modify a property within a multi-layer nested object, deconstruct it layer by layer as follows:

class MyComponent extends React.Component {
  constructor(props) {
    super(props)
    this.state = { person: { city: { cityName: 'Beijing'}}}}handleClick() {
    this.setState((prevState) = > {
      return {
        person: {
          ...prevState.person,
          city: {
            ...prevState.person.city,
            cityName: 'Shanghai'}}}})}render() {
    return (
      <div>
        <h2>{this.state.person.city.cityName}</h2>
        <button onClick={this.handleClick.bind(this)}>Modify the city</button>
      </div>)}}Copy the code

As you can see from the code above, as the state object structure gets deeper, changing the innermost state child node becomes more cumbersome to write.

We can come up with a simple solution to make a deep copy of a new object and then directly change the properties of the new object, such as using cloneDeep with LoDash:

handleClick() {
  this.setState((prevState) = > {
    const newState = _.cloneDeep(prevState)
    newState.person.city.cityName = 'Shanghai'
    return newState
  })
}
Copy the code

However, this scheme has significant performance problems. No matter which property of the object you want to update, make a deep copy of the entire object each time. Deep copy can cause performance problems when objects are particularly large.

Another solution is to use Immutable libraries such as immer.js to simplify development. It avoids deep-copying all attributes and only modifies the target attributes.

/ / installation
npm i immer
Copy the code

When we call IMmer’s API Produce, the IMmer will temporarily store our target object internally. And expose a draft so we can make changes on the draft and then go back. Here’s a simple use:

import { produce } from 'immer'
handleClick() {
  this.setState((prevState) = > {
    return produce(prevState, (draftState) = > {
      draftState.person.city.cityName = 'Shanghai'})})}Copy the code

6.6 Principles of setState(

  1. Object mode can be used if the new state does not depend on the previous state.

  2. If the new state depends on the way the function is used in the previous state.

  3. If you need to get the latest state data in setState(), you can get it in the second callback function.

Seven, props

Components are independent and reusable. A component may be used in different places. The requirements for this component may vary in different scenarios, so pass in different configuration items for the same component.

The React props can do this. Each component can accept a props argument, which is an object that contains all the configurations for the component:

class Person extends React.Component {
  render() {
    const { name, age } = this.props
    return (
      <ul>
        <li>{name}</li>
        <li>{age}</li>
      </ul>
    )
  }
}
ReactDOM.render(<Person name="Zhang" age={20} />.document.getElementById('root'))
Copy the code

Component parameters are obtained internally by means of this.props. When using a component, all properties are used as the keys of the props object. We can also specify a default value using the static property of defaultProps:

class Person extends React.Component {
  // Specify a default value
  static defaultProps = {
    name: 'bill'.age: 30
  }
  render() {
    const { name, age } = this.props
    return (
      <ul>
        <li>{name}</li>
        <li>{age}</li>
      </ul>
    )
  }
}
ReactDOM.render(<Person age={20} />.document.getElementById('root'))
Copy the code

If no name attribute is passed in, the default custom attribute value is used:

7.1. Checking the tag attribute type of props

Sometimes we need to restrict the type of props passed in. Starting with Act16, type checking was removed and split into another library. Need separate installation:

npm install prop-types
Copy the code

Or directly into the browser:

<script src="https://unpkg.com/[email protected]/prop-types.js"></script>
Copy the code

Here’s a simple little example:

class Person extends React.Component {
  // Check the attributes
  static propTypes = {
    name: PropTypes.string.isRequired, // Name must be passed and is a string
    getName: PropTypes.func // getName is a function because 'function' is the keyword, so use 'func'
  }
  render() {
    const { name, age } = this.props
    return (
      <ul>
        <li>{name}</li>
        <li>{age}</li>
      </ul>
    )
  }
}
ReactDOM.render(<Person name="Zhang" age={20} />.document.getElementById('root'))
Copy the code

7.2. Props cannot be changed

Once passed in, props cannot be modified inside the component. However, the parent component can pass in new props by actively modifying the state re-rendering. As follows:

/ / child component
function Child(props) {
  return <div>{props.name}</div>
}
/ / the parent component
class Parent extends React.Component {
  constructor(props) {
    super(props)
    this.state = { name: 'Joe'}}// Change the name to render again
  changeName() {
    this.setState({ name: 'bill'})}render() {
    return (
      <div>
        <Child name={this.state.name} />
        <button onClick={this.changeName.bind(this)}>Modify the name</button>
      </div>
    )
  }
}
ReactDOM.render(<Parent />.document.getElementById('root'))
Copy the code

Eight, Ref

React doesn’t fully satisfy all DOM manipulation requirements, and sometimes we still need to work with DOM. For example, the DOM API of input.focus() is invoked to automatically focus to an input field after entering the page. React provides several ways to get the DOM nodes of mounted elements.

Don’t abuse refs. For example, use it to manipulate the UI the traditional way: find the DOM -> update the DOM.

8.1. The format is a string

Specify a name by setting a ref attribute above the DOM element, and then access the corresponding DOM element through this.refs.name.

class MyComponent extends React.Component {
  // Click to get focus
  handleClick() {
    this.refs.textInput.focus()
  }
  render() {
    return (
      <div>
        <input ref="textInput" />
        <button onClick={this.handleClick.bind(this)}>Click me</button>
      </div>
    )
  }
}
ReactDOM.render(<MyComponent />.document.getElementById('root'))
Copy the code

It has been officially deprecated because of some problems with string refs. It is outdated and may be removed in a future release.

8.2. Callback form

The ref attribute sets this to a callback function that receives the current DOM element:

class MyComponent extends React.Component {
  handleClick() {
    this.textInput.focus()
  }
  render() {
    return (
      <div>
        <input ref={(element)= > (this.textInput = element)} />
        <button onClick={this.handleClick.bind(this)}>Click me</button>
      </div>
    )
  }
}

ReactDOM.render(<MyComponent />.document.getElementById('root'))
Copy the code

If the ref callback is defined as an inline function, it is executed twice during the update process, passing in the argument NULL the first time, and then the argument DOM element the second time. This is because a new function instance is created each time it renders, so React clears the old ref and sets the new one. This can be avoided by defining the ref callback function as a class binding function, but in most cases it is irrelevant.

8.3. Form createRef()

The React. CreateRef () API, introduced in React 16.3, can also be used to retrieve DOM elements by calling createRef() and returning a container that can store the node identified by ref. The container can only hold one element node:

class MyComponent extends React.Component {
  constructor(props) {
    super(props)
    this.myRef = React.createRef()
  }
  handleClick() {
    this.myRef.current.focus()
  }
  render() {
    return (
      <div>
        <input ref={this.myRef} />
        <button onClick={this.handleClick.bind(this)}>Click me</button>
      </div>
    )
  }
}
ReactDOM.render(<MyComponent />.document.getElementById('root'))
Copy the code

Ref gets the component instance

A Ref can retrieve not only DOM elements, but also instances of child components. Here are three different ways to get child component instances:

import React, { createRef } from 'react'

/ / child component
class Child extends React.Component {
  handleChild() {
    console.log('Child Component')}render() {
    return <div>The Child components</div>}}/ / the parent component
class Father extends React.Component {
  constructor(props) {
    super(props)
    this.childRef3 = createRef()
  }
  componentDidMount() {
    this.refs.childRef1.handleChild()     //Child Component
    this.childRef2.handleChild()          //Child Component
    this.childRef3.current.handleChild()  //Child Component
  }
  render() {
    return (
      <div>
        <Child ref="childRef1" />
        <Child ref={(element)= > (this.childRef2 = element)} />
        <Child ref={this.childRef3} />
      </div>)}}Copy the code

Ix. Event handling

React binds events in a similar way to HTML, but with a camel name. Here is a small example of a sum of numbers:

class AddCount extends React.Component {
  constructor(props) {
    super(props)
    this.state = { count: 0}}handleClick(e) {
    this.setState({ count: this.state.count + 1})}render() {
    return (
      <div>
        <p>Count: {this.state.count}</p>
        <button onClick={this.handleClick.bind(this)}>Click me</button>
      </div>
    )
  }
}
ReactDOM.render(<AddCount />.document.getElementById('root'))
Copy the code

Note that you explicitly call bind(this) to bind the event function context to the component instance.

Collect form data

React processing forms can be managed by managed and uncontrolled components. The following two methods are introduced respectively.

10.1 controlled Components

In React, form elements are maintained through the component’s state property. And calls setState() to update the data based on user input. The form input elements that React controls their values in this way are called controlled components. Here is an example of changing a user name:

class MyComponent extends React.Component {
  constructor(props) {
    super(props)
    this.state = { userName: 'init userName'}}// Change the user name
  changeUsername(e) {
    this.setState({ userName: e.target.value })
  }
  / / submit
  handleSubmit(e) {
    e.preventDefault()
    const { userName } = this.state
    console.log('Your username is:${userName}`)}render() {
    return (
      <form onSubmit={this.handleSubmit.bind(this)}>
        <input type="text" value={this.state.userName} name="username" onChange={this.changeUsername.bind(this)} />
        <button type="submit">submit</button>
      </form>
    )
  }
}
ReactDOM.render(<MyComponent />.document.getElementById('root'))
Copy the code

In React, data flows in one direction. The data for the form comes from the component’s state and is passed in through props, which is also known as one-way data binding. We then write the new form data back to the component’s state via the onChange event handler, completing two-way data binding.

React-controlled components update state

  1. By starting withstateTo set the default values for the form.
  2. Called whenever the value of the form changesonChangeEvent handler.
  3. Get the latest value from the event handler and usesetState()updatestate.
  4. setState()Triggers a re-rendering of the view to complete the update of the form component values.

In most cases, we still use controlled components to process form data.

10.2 Uncontrolled Components

An uncontrolled component is an input class element that does not maintain data through state and uses ref to retrieve data from a DOM node:

class MyComponent extends React.Component {
  constructor(props) {
    super(props)
    this.userNameNode = React.createRef()
  }
  handleSubmit(e) {
    e.preventDefault()
    console.log('Your username is:The ${this.userNameNode.current.value}`)}render() {
    return (
      <form onSubmit={this.handleSubmit.bind(this)}>
        <input type="text" name="username" ref={this.userNameNode} />
        <button type="submit">submit</button>
      </form>
    )
  }
}
ReactDOM.render(<MyComponent />.document.getElementById('root'))
Copy the code

Component life cycle

Each component in React contains component lifecycle methods that are executed at specific phases of the runtime.

Component lifecycles fall into four categories:

  1. Mount (initialization) phase.
  2. Update phase.
  3. Destruction phase.
  4. Error phase.

Some lifecycle methods have been deprecated or modified due to React version issues. The component lifecycle is described in the following sections based on the version.

React Lifecycle before React 16.3

To give you an overview of the old life cycle, take a look:

Mount the stage

When a component instance is created and inserted into the DOM, the component lifecycle is invoked in the following order:

  1. constructor()Constructor call.
  2. componentWillMount(): called before the component is about to be mounted.
  3. render(): Initializes rendering.
  4. componentDidMount(): After the component is mounted (insertDOMTree) immediately called.
class LifeCycle extends React.Component {
  constructor(props) {
    super(props)
    console.log('1. construct')}componentWillMount() {
    console.log('2. componentWillMount')}render() {
    console.log('3. render')
    return <div>React Old life cycle</div>
  }
  componentDidMount() {
    console.log('4. componentDidMount')
  }
}
ReactDOM.render(<LifeCycle />.document.getElementById('root'))
Copy the code

The component mount phase is output as follows:

1. construct
2. componentWillMount
3. render
4. componentDidMount
Copy the code

Update the stage

The update phase of a component is the process by which setState() causes React to re-render the component and apply its changes to DOM elements. React also provides a series of lifecycle functions that allow you to perform operations during component updates. Updates are triggered when a component’s state changes. Component updates are called in the lifecycle order as follows:

  1. shouldComponentUpdate(): whenpropsorstateCalled when changes occur.
  2. componentWillUpdate(): called before the component begins rerendering.
  3. render(): Re-render after update.
  4. componentDidUpdate(): Components are re-rendered and changed to realDOMCall later.

Note that shouldComponentUpdate() uses this method to control whether or not the component is rerendered. The default returns true, meaning the component is rerendered each time it changes. If this function returns false, the component will not be rerendered. This life cycle is useful for React performance tuning (more on that later).

class LifeCycle extends React.Component {
  constructor(props) {
    super(props)
    this.state = { count: 0}}handleClick() {
    this.setState({ count: this.state.count + 1})}shouldComponentUpdate() {
    console.log('1. shouldComponentUpdate')
    return true
  }
  componentWillUpdate() {
    console.log('2. componentWillUpdate')}render() {
    console.log('3. render')
    return (
      <div>
        <p>Count: {this.state.count}</p>
        <button onClick={this.handleClick.bind(this)}>Click me</button>
      </div>)}componentDidUpdate() {
    console.log('4. componentDidUpdate')
  }
}
ReactDOM.render(<LifeCycle />.document.getElementById('root'))
Copy the code

When the handleClick click event is triggered, the output is as follows:

1. shouldComponentUpdate
2. componentWillUpdate
3. render
4. componentDidUpdate
Copy the code

forceUpdate()

We can also re-render components using forceUpdate(). Forcing updates skips shouldComponentUpdate(). ForceUpdate () should be avoided in general.

Updates to props changes

If you have a father and son components, pass an argument by props, the child component lifecycle update will add a componentWillReceiveProps () life cycle. This is called before the child component receives the new props from the parent component.

ComponentWillReceiveProps (nextProps) receives a parameter, the parameter is updated props objects:

/ / the parent component
class Parent extends React.Component {
  constructor(props) {
    super(props)
    this.state = { name: 'Joe'}}changeName() {
    this.setState({ name: 'bill'})}render() {
    return (
      <div>
        <Child name={this.state.name} />
        <button onClick={this.changeName.bind(this)}>Modify the name</button>
      </div>)}}/ / child component
class Child extends React.Component {
  componentWillReceiveProps() {
    console.log('1. componentWillReceiveProps')}shouldComponentUpdate() {
    console.log('2. shouldComponentUpdate')
    return true
  }
  componentWillUpdate() {
    console.log('3. componentWillUpdate')}render() {
    console.log('4. render')
    return <div>I am a child component and I receive {this.props. Name}</div>
  }
  componentDidUpdate() {
    console.log('5. componentDidUpdate')
  }
}
ReactDOM.render(<Parent />.document.getElementById('root'))
Copy the code

When a click event is triggered, it is printed as follows:

1. componentWillReceiveProps
2. shouldComponentUpdate
3. componentWillUpdate
4. render
5. componentDidUpdate
Copy the code

Initialize the transfer props will not perform when componentWillReceiveProps (). This method is called if the parent causes the component to be re-rendered, even if the props have not changed.

Unloading phase

The following component lifecycle is invoked when a component is removed from the DOM:

  1. componentWillUnmount(): called directly before the component is uninstalled and destroyed.
class LifeCycle extends React.Component {
  handleClick() {
    / / unloading
    ReactDOM.unmountComponentAtNode(document.getElementById('root'))}render() {
    return <button onClick={this.handleClick.bind(this)}>Component uninstall</button>
  }
  componentWillUnmount() {
    console.log('1. componentWillUnmount')}}const App = () = > <LifeCycle />
export default App
Copy the code

When the click event handleClick is triggered, it prints:

1. componentWillUnmount
Copy the code

React v16.3 New life cycle

To get an overview of the new life cycle, start with an image:

The new version of the lifecycle has three methods renamed:

  1. componentWillMount()Change toUNSAFE_componentWillMount()
  2. componentWillReceiveProps()Change toUNSAFE_componentWillReceiveProps()
  3. componentWillUpdateChange toUNSAFE_componentWillUpdate()

The UNSAFE_ prefix must be added after the new version, otherwise the three health hooks may not be available.

The new version adds two static method lifecycles:

  1. static getDerivedStateFromProps()
  2. static getSnapshotBeforeUpdate()

static getDerivedStateFromProps()

GetDerivedStateFromProps () means to get state from props and map the passed props to state.

GetDerivedStateFromProps () is called both during the initial mount and during subsequent updates.

State must be initialized when using getDerivedStateFromProps(). It should return an object to update state, and if null is returned, nothing is updated.

GetDerivedStateFromProps (nextProps,nextState) accepts two arguments:

  1. nextProps: the latestprops
  2. nextState: the lateststate

This function is not affected when null is returned:

class LifeCycle extends React.Component {
  constructor(props) {
    super(props)
    this.state = { count: 0}}static getDerivedStateFromProps(props, state) {
    return null
  }
  render() {
    return <div>{this.state.count}</div>
  }
}
ReactDOM.render(<LifeCycle />.document.getElementById('root'))
Copy the code

When returning props as a state object, the value of the state in the component depends on the value of props at any time:

class LifeCycle extends React.Component {
  constructor(props) {
    super(props)
    this.state = { count: 0}}// The state value in the component depends on props, the setting value is invalid
  handleClick() {
    this.setState({ count: this.state.count + 1})}static getDerivedStateFromProps(props, state) {
    return props // Return props:{count: 100}
  }
  render() {
    return <div onClick={this.handleClick.bind(this)}>Count: {this. State. The count}</div>
  }
}

ReactDOM.render(<LifeCycle count={100} />.document.getElementById('root'))
Copy the code

Count ={100} is passed as props, which is returned by getDerivedStateFromProps instead of this. State ={count: 0} :

static getSnapshotBeforeUpdate()

GetSnapshotBeforeUpdate () is called before the last render output (committed to the DOM node) to get the snapshot before the update.

Must return a Snapshot value(any value) or NULL. The returned value is passed as an argument to the componentDidUpdate() hook function.

You must also define the componentDidUpdate() hook function when using getSnapshotBeforeUpdate().

The componentDidUpdate(prevProps, prevState, snapShotValue) function accepts three arguments:

  1. prevPropsA:props
  2. prevStateA:state
  3. snapShotValue:getSnapshotBeforeUpdate()Snapshot value returned.

The Snapshot value returned by getSnapshotBeforeUpdate() is received by the componentDidUpdate() method:

class MyComponent extends React.Component {
  constructor(props) {
    super(props)
    this.state = { count: 0}}handleClick(e) {
    this.setState({ count: this.state.count + 1})}// Take the snapshot before the update
  getSnapshotBeforeUpdate() {
    return 'Joe'
  }
  componentDidUpdate(prevProps, prevState, snapShotValue) {
    console.log(snapShotValue) / / zhang SAN
  }
  render() {
    return (
      <div>
        <p>Count: {this.state.count}</p>
        <button onClick={this.handleClick.bind(this)}>Click me</button>
      </div>
    )
  }
}
ReactDOM.render(<MyComponent count={100} />.document.getElementById('root'))
Copy the code

You can use getSnapshotBeforeUpdate() to implement a case where the current position is always positioned while the list height continues to increase:

class News extends React.Component {
  constructor(props) {
    super(props)
    this.state = { news: []}this.newWrapRef = React.createRef()
  }
  componentDidMount() {
    // Generates one news per second
    setInterval(() = > {
      const { news } = this.state
      const oneNew = 'news' + news.length
      this.setState({ news: [oneNew, ...news] })
    }, 1000)}render() {
    return (
      <ul ref={this.newWrapRef} style={{ height: '180px', border: '1px solid red', overflow: 'auto' }}>
        {this.state.news.map((item, index) => {
          return (
            <li key={index} style={{ height: '30px' }}>
              {item}
            </li>)})}</ul>)}getSnapshotBeforeUpdate() {
    // Get the container's scrollHeight before rerendering
    return this.newWrapRef.current.scrollHeight
  }
  componentDidUpdate(prevProps, prevState, snapshotValue) {
    // The new scrollHeight minus the previous scrollHeight
    const newScrollHeight = this.newWrapRef.current.scrollHeight - snapshotValue
    // Reset the latest scrollTop
    this.newWrapRef.current.scrollTop += newScrollHeight
  }
}

ReactDOM.render(<News count={100} />.document.getElementById('root'))
Copy the code

Mount the stage

When a component instance is created and inserted into the DOM, the component lifecycle is invoked in the following order:

  1. constructor()Constructor call.
  2. getDerivedStateFromProps(): called during initial mount and subsequent updates.
  3. render(): Initializes rendering.
  4. componentDidMount(): After the component is mounted (insertDOMTree) immediately called.
class LifeCyCle extends React.Component {
  constructor(props) {
    super(props)
    this.state = {}
    console.log('1. constructor')}static getDerivedStateFromProps() {
    console.log('2. getDerivedStateFromProps')
    return null // There must be a return value
  }

  render() {
    console.log('3. render')
    return <div>React New life cycle</div>
  }

  componentDidMount() {
    console.log('4. componentDidMount')
  }
}
ReactDOM.render(<LifeCyCle />.document.getElementById('root'))
Copy the code

The component mount phase is output as follows:

1. constructor
2. getDerivedStateFromProps
3. render
4. componentDidMount
Copy the code

Update the stage

Component updates are called in the lifecycle order as follows:

  1. getDerivedStateFromProps(): is called during the initial mount and subsequent updates.
  2. shouldComponentUpdate(): whenpropsorstateCalled when changes occur.
  3. render()Render after status update.
  4. getSnapshotBeforeUpdate(): called before the last render output (committed to the DOM node).
  5. componentDidUpdate(): Components are re-rendered and changed to realDOMCall later.
class LifeCycle extends React.Component {
  constructor(props) {
    super(props)
    this.state = { count: 0}}handleClick() {
    this.setState({ count: this.state.count + 1})}static getDerivedStateFromProps() {
    console.log('1. getDerivedStateFromProps')
    return null
  }
  shouldComponentUpdate() {
    console.log('2. shouldComponentUpdate')
    return true
  }
  render() {
    console.log('3. render')
    return (
      <div>
        <p>Count: {this.state.count}</p>
        <button onClick={this.handleClick.bind(this)}>Click me</button>
      </div>)}getSnapshotBeforeUpdate() {
    console.log('4. getSnapshotBeforeUpdate')
    return null
  }
  componentDidUpdate() {
    console.log('5. componentDidUpdate')
  }
}
ReactDOM.render(<LifeCycle />.document.getElementById('root'))
Copy the code

When the click event is triggered, handleClick prints the following:

1. getDerivedStateFromProps
2. shouldComponentUpdate
3. render
4. getSnapshotBeforeUpdate
5. componentDidUpdate
Copy the code

Unloading phase

The life cycle of the destruction phase of the new and old versions has not changed.

12. False boundaries

JavaScript errors in part of the UI should not crash the entire app, but React 16 introduces a new concept called Error Boundaries. Error boundaries are a React component that can be used to catch descendant component errors and render alternate pages. It can only catch errors that occur in the life cycle of subsequent components.

If the child component gets an error, the parent component needs to render the standby UI with getDerivedStateFromError() and print the error message with componentDidCatch(). Here is an example of a parent-child component:

import React from 'react'

/ / child component
class Child extends React.Component {
  constructor(props) {
    super(props)
    this.state = { userList: 'abc'}}render() {
    return (
      <div>
        <h2>I am the Child component</h2>} {this.state.userlist.map ((item) => {return<span key={item.id}>{item.name}</span>
        })}
      </div>)}}/ / the parent component
class Parent extends React.Component {
  constructor(props) {
    super(props)
    this.state = { hasError: false}}// If an error occurs in a child component, a call to getDerivedStateFromError is raised with an error message in the argument
  static getDerivedStateFromError(error) {
    console.log(error)
    return { hasError: error } // An error message needs to be returned
  }
  componentDidCatch() {
    // Error logs can be reported to the server
  }
  render() {
    return (
      <div>
        <h2>I'm the Parent component</h2>} {this.state.haserror?<span>There is an error</span> : <Child />}
      </div>)}}Copy the code

PureComponent Optimization

When the parent component’s data is modified and re-rendered, the child component will also re-render without using any of the parent’s data:

import React from 'react'

class Parent extends React.Component {
  constructor(props) {
    super(props)
    this.state = { name: 'Joe'}}handleChangeName(e) {
    this.setState({ name: 'bill'})}render() {
    console.log('Parent render')
    return (
      <div>
        <button onClick={this.handleChangeName.bind(this)}>Modify the name</button>
        <Child />
      </div>)}}class Child extends React.Component {
  render() {
    console.log('Child render')
    return <div>I am the Child component</div>}}Copy the code

Child Render is output when the handleChangeName() method is triggered by clicking on the button. Any data that is not used by the child component is rerendered.

One other thing to note is that the component is also re-rendered when this.setState({}) is called with no value.

ShouldComponentUpdate () we can use shouldComponentUpdate() to optimize, with the parent comparing state and the child comparing props to determine whether to re-render:

import React from 'react'

/ / the parent component
class Parent extends React.Component {
  constructor(props) {
    super(props)
    this.state = { name: 'Joe'}}handleChangeName(e) {
    this.setState({ name: 'bill'})}/ * * *@param {*} NextProps Latest props *@param {*} NextState The latest state */
  shouldComponentUpdate(nextProps, nextState) {
    // State's name attribute does not need to be rerendered if it does not change
    return! (this.state.name === nextState.name)
  }

  render() {
    console.log('Parent render')
    return (
      <div>
        <button onClick={this.handleChangeName.bind(this)}>Modify the name</button>
        <Child name={this.state.name} />
      </div>)}}/ / child component
class Child extends React.Component {
  shouldComponentUpdate(nextProps, nextState) {
    // The name property of props does not need to be re-rendered if it does not change
    return! (this.props.name === nextProps.name)
  }
  render() {
    console.log('Child render')
    return <div>I am Child component: {this.props. Name}</div>}}Copy the code

As you can see from the above code, if the object has multiple attributes, you also need to compare each attribute. This is a very cumbersome way to write a project. React provides the React.PureComponent. The react. PureComponent implements shouldComponentUpdate as a shallow comparison between prop and state.

import React from 'react'

class Parent extends React.PureComponent {
  constructor(props) {
    super(props)
    this.state = { name: 'Joe'}}handleChangeName(e) {
    this.setState({ name: 'bill'})}render() {
    console.log('Parent render')
    return (
      <div>
        <button onClick={this.handleChangeName.bind(this)}>Modify the name</button>
        <Child name={this.state.name} />
      </div>)}}class Child extends React.PureComponent {
  render() {
    console.log('Child render')
    return <div>I am Child component: {this.props. Name}</div>}}Copy the code

ShouldComponentUpdate () in react.pureComponent is only a superficial comparison of objects.

If the object contains complex data structures, it is possible to generate false comparisons because you cannot examine the underlying differences.

Use React.PureComponent only when props and state are simple, or call forceUpdate() when the underlying data structure changes to ensure that the component is updated properly.

Don’t modify the data directly, regenerate it.

14, Render Props

A component with Render Prop takes a function that returns the React element and implements its own rendering logic within the component by calling this function.

If you want to write something to a component tag, you need to get the tag body from the component props. Children.

import React from 'react'

/ / the parent component
class Parent extends React.Component {
  render() {
    return (
      <div>
        <p>I'm the Parent component</p>
        <Child>Hello!</Child>
      </div>)}}/ / child component
class Child extends React.Component {
  render() {
    return (
      <div>
        <p>I am the Child component</p>
        {this.props.children}
      </div>)}}Copy the code

The Child component will print Hello:

Let’s say there is a requirement for nested components within components to form parent-child relationships like the following:

class App extends React.Component {
  render() {
    return (
      <B>
        <C></C>
      </B>)}}Copy the code

To pass the state data of B to C, we can use this.props. Render to call our Render function:

import React from 'react'

class App extends React.Component {
  render() {
    return (
      <div>
        <p>I'm an App component</p>{/* Write the render callback and receive the data */}<B render={(name)= > <C name={name} />} / ></div>)}}/ / child component
class B extends React.Component {
  constructor(props) {
    super(props)
    this.state = { name: 'Joe'}}render() {
    return (
      <div>
        <p>I'm component B</p>{/* Call the render() method of props and pass the data */} {this.props. Render (this.state.name)}</div>)}}/ / sun components
class C extends React.Component {
  render() {
    return (
      <div>
        <p>I am C component, and I get name from B component: {this.props. Name}</p>
      </div>)}}Copy the code

C Props = Props (); C Props = Props ();

Render Props are similar to Vue’s slot technology.

React Scaffolding

Create-react-app, similar to vue-cli for Vue:

npm install -g create-react-app
// Create a new project with create-react-app
create-react-app my-react-app
Copy the code

After the installation is successful, NPM start or YARN start can start the project.

Package. json Install the React dependencies as follows:

As you can see from package.json dependencies, the scaffolding tool installs dependencies required by React by default. Here’s how these key core dependencies work:

  1. reactIs:ReactCore library.
  2. react-dom: be responsible forWebOf the pageDOMOperation.
  3. react-scripts: Generates all dependencies for the project. For example,babel.css-loader.webpackAnd so on from development to packaging front-end engineering needsreact-scriptsIt’s all done for us.

Xvi. TodoList function

Here we can create a new project using the create-react-app scaffolding todo a todoList function:

Since we haven’t touched on state management yet, we can pass data between sibling components and implement functionality via parent components. First, define the following four components:

  1. HeaderComponents.
  2. ItemComponent that represents each item in the list.
  3. ListList components.
  4. FooterComponents.

App.js

/* app.css */
#root {
  display: flex;
  justify-content: center;
}

.todo-container {
  margin-top: 20px;
  min-height: 240px;
  min-width: 420px;
  border: 1px solid #ccc;
  border-radius: 4px;
  padding: 20px;
  box-sizing: border-box;
}
Copy the code
// App.js
import React from 'react'
import Header from './components/Header'
import List from './components/List'
import './app.css'
import Footer from './components/footer'
class App extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      list: [{ id: 1.name: 'JS'.isChecked: false}}}]/ / add a todo
  addTodo(todo) {
    const newList = [todo, ...this.state.list]
    this.setState({ list: newList })
    console.log(todo)
  }
  / / modify todo
  editTodo(item, isChecked) {
    const newTodo = this.state.list.map((el) = > {
      return el.id === item.id ? { ...el, isChecked } : el
    })
    this.setState({ list: newTodo })
  }
  / / delete todo
  deleteTodo(item) {
    const newTodo = this.state.list.filter((el) = >el.id ! == item.id)this.setState({ list: newTodo })
  }
  // Delete all selections
  deleteChecked() {
    const newTodos = this.state.list.filter((el) = >! el.isChecked)this.setState({ list: newTodos })
  }
  // Delete all elements
  deleteCheckedAll() {
    this.setState({ list: []})}// Select all and reverse
  changeTodoAll(isChecked) {
    const newTodos = this.state.list.map((el) = > {
      return { ...el, isChecked }
    })
    this.setState({ list: newTodos })
  }
  render() {
    return (
      <div className="todo-container">
        <Header addTodo={this.addTodo.bind(this)} />
        <List list={this.state.list} editTodo={this.editTodo.bind(this)} deleteTodo={this.deleteTodo.bind(this)} />
        <Footer list={this.state.list} deleteChecked={this.deleteChecked.bind(this)} deleteCheckedAll={this.deleteCheckedAll.bind(this)} changeTodoAll={this.changeTodoAll.bind(this)} />
      </div>)}}export default App
Copy the code

The Header component

/* components/Header/index.css */
.header {
  margin-bottom: 20px;
}

.header input {
  box-sizing: border-box;
  padding: 8px;
  width: 100%;
  border: 1px solid #ccc;
}
.header input:focus {
  outline: none;
  border-color: #409eff;
}
Copy the code
// components/Header/index.jsx

import React from 'react'
import './index.css'

export default class Header extends React.Component {
  // Enter to add elements
  handleKeyUp(e) {
    if(e.keyCode ! = =13) return
    const val = e.target.value
    if (val.trim() === ' ') return
    const todo = {
      id: Math.random(), // Random numbers are simply used here and are not recommended in actual development
      name: val,
      isChecked: false
    }
    // Call the function passed by the parent component props to add data
    this.props.addTodo(todo)
    e.target.value = ' '
  }

  render() {
    return (
      <div className="header">{/* Input box */}<input onKeyUp={this.handleKeyUp.bind(this)} type="text" autoComplete="off" name="myInput" placeholder="Please enter the task name and press Enter to confirm." />
      </div>)}}Copy the code

The List component

/* components/List/index.css */
.list-container {
  border: 1px solid #ccc;
  padding: 20px;
}
Copy the code
// components/List/index.jsx
import React from 'react'
import Item from '.. /Item'
import './index.css'
export default class List extends React.Component {
  render() {
    const { list, editTodo, deleteTodo } = this.props
    return (
      <div className="list-container">{list.map((item) => {/* Uses the item component */ return<Item key={item.id} item={item} editTodo={editTodo} deleteTodo={deleteTodo} />
        })}
      </div>)}}Copy the code

The Item component

// components/Item/index.jsx
import React from 'react'
export default class Item extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      isShowBtn: false}}// Mouse over the display
  handleMouseEnter(e) {
    this.setState({ isShowBtn: true})}// Mouse remove hide
  handleMouseLeave(e) {
    this.setState({ isShowBtn: false})}handleChange(e, item) {
    // Notify the parent component to change the status
    this.props.editTodo(item, e.target.checked)
  }
  / / delete
  handleDelete(e, item) {
    this.props.deleteTodo(item)
  }
  render() {
    const { item } = this.props
    const { isShowBtn } = this.state
    return (
      <div
        onMouseEnter={this.handleMouseEnter.bind(this)}
        onMouseLeave={this.handleMouseLeave.bind(this)}
        style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '4px 0'}} >
        <div>
          <input type="checkbox" id={item.id} checked={item.isChecked} onChange={(e)= > this.handleChange.call(this, e, item)} />
          <label style={{ paddingLeft: '10px'}}htmlFor={item.id}>
            {item.name}
          </label>
        </div>
        <button onClick={(e)= >this.handleDelete.call(this, e, item)} style={{ display: isShowBtn ? 'Block' : 'None'}}> Delete</button>
      </div>)}}Copy the code

Footer component

// components/Footer/index.jsx

import React from 'react'
export default class Footer extends React.Component {
  constructor(props) {
    super(props)
    this.state = {}
  }
  handleChange(e) {
    // Notify the parent element of the modification
    this.props.changeTodoAll(e.target.checked)
  }
  render() {
    const { list, deleteChecked, deleteCheckedAll } = this.props
    // The selected list
    const checkeds = list.filter((item) = > item.isChecked)
    return (
      <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginTop: '20px' }}>
        <div>
          <input type="checkbox" checked={checkeds.length= = =list.length && list.length! = =0} onChange={this.handleChange.bind(this)} name="all" />
          <span>Completed {checkeds.length} / all {list.length}</span>
        </div>
        <div>
          <button onClick={deleteChecked} style={{ marginRight: '5px' }}>Delete all selected</button>
          <button onClick={deleteCheckedAll}>Delete all</button>
        </div>
      </div>)}}Copy the code

17, Redux

Redux is a library for state management (not the official React library). It stores the entire application state in the Store. A component can dispatch actions to a store. Other components can refresh their views by subscribing to states in the store:

The whole Redux can be considered as a restaurant, with React Components as a table of guests, Action Creators as a server, Store as a lobby manager, and Reducers as a back chef in charge of cooking. The guest orders food to the waiter, the waiter notifies the lobby manager, manager Tang notifies the back kitchen to cook, and sends the food to the table through the lobby manager:

  • action

Action represents the action object. It contains two attributes:

  1. type: Identifies an attribute. The value is a string and unique.
  2. data: Data to be transmitted.
  • store

Store stores state (sets of data). And link state, Action and Reducer together. There is only one Store object for the entire application.

  • reducer

Reducer is used to initialize state and process state. When processing states, a new state pure function is generated based on the old state and action.

Installation:

npm install --save redux
Copy the code

Here is a small example of addition and subtraction:

Create redux/store.js first:

// redux/store.js
// createStore is introduced to create the core store object in Redux
import { createStore } from 'redux'

// Define a constant value of type type
export const INCREMENT = 'increment' // 加1
export const DECREMENT = 'decrement' / / minus 1

/** * create action */
export const actions = {
  increment(data) {
    return { type: INCREMENT, data }
  },
  decrement(data) {
    return { type: DECREMENT, data }
  }
}

/** * Create reducer *@param {*} PreState Indicates the previous state *@param {*} Action Action object *@returns* /
function countReducer(preState = 0, action) {
  /** * Get type and data * type from the action object. The default initialization is similar to @@redux/ initc.q. 3. O.K.G a random value */
  const { type, data } = action
  switch (type) {
    case INCREMENT:
      return preState + data
    case DECREMENT:
      return preState - data
    default:
      return preState
  }
}

// Create a store and pass in the corresponding countReducer
const store = createStore(countReducer)

export default store

Copy the code

Then use it in app.js:

import React from 'react'
// Introduce store and Action
import store, { actions } from './redux/store'
class App extends React.Component {
  /** * Redux does not render the page by default after calling an action with dispatch to change the state. It listens for state changes with store.subscribe() */
  componentDidMount() {
    // Listen for state changes in redux, and call setState() to re-render the page whenever it changes
    store.subscribe(() = > {
      this.setState({})
    })
  }
  increment() {
    store.dispatch(actions.increment(1))}decrement() {
    store.dispatch(actions.decrement(1))}render() {
    return (
      <div>
        <h1>{store.getState()}</h1>
        <button onClick={this.increment.bind(this)}>Add 1</button>
        <button onClick={this.decrement.bind(this)}>Minus 1</button>
      </div>)}}export default App
Copy the code

17.1. Asynchronous Action

Actions can return functions as well as objects. Synchronous actions for objects and asynchronous actions for functions.

If you want to use asynchronous methods in your Actions. Redux-thunk middleware is required:

/ / installation
npm install -S redux-thunk
Copy the code

After installation, use the applymiddleware() middleware using Redux as the second parameter to createStore() :

// Introduce react-thunk to support asynchronous actions
import thunk from 'redux-thunk'
// Pass the corresponding countReducer and redux-thunk
const store = createStore(countReducer, applyMiddleware(thunk))
Copy the code

Here is a small example of asynchronous addition and subtraction:

Create redux/store.js first:

// createStore is introduced to create the core store object in Redux
import { createStore, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'

// Define a constant value of type type
export const INCREMENT = 'increment' // 加1
export const DECREMENT = 'decrement' / / minus 1

/** * create action */
export const actions = {
  increment(data) {
    return { type: INCREMENT, data }
  },
  decrement(data) {
    return { type: DECREMENT, data }
  },
  incrementAsync(data) {
    return (dispatch) = > {
      setTimeout(() = > {
        dispatch(this.increment(data)) // Synchronous actions are usually called in asynchronous actions
      }, 500)}},decrementAsync(data) {
    return (dispatch) = > {
      setTimeout(() = > {
        dispatch(this.decrement(data)) // Synchronous actions are usually called in asynchronous actions
      }, 500)}}}/** * Create reducer *@param {*} PreState Indicates the previous state *@param {*} Action Action object *@returns* /
function countReducer(preState = 0, action) {
  /** * Get type and data * type from the action object. The default initialization is similar to @@redux/ initc.q. 3. O.K.G a random value */
  const { type, data } = action
  switch (type) {
    case INCREMENT:
      return preState + data
    case DECREMENT:
      return preState - data
    default:
      return preState
  }
}

// Create a store and pass in countReducer and applyMiddleware
const store = createStore(countReducer, applyMiddleware(thunk))

export default store
Copy the code

Then use it in app.js:

import React from 'react'
// Introduce store and Action
import store, { actions } from './redux/store'
class App extends React.Component {
  /** * Redux does not render the page by default after calling an action with dispatch to change the state. It listens for state changes with store.subscribe() */
  componentDidMount() {
    // Listen for state changes in redux, and call setState() to re-render the page whenever it changes
    store.subscribe(() = > {
      this.setState({})
    })
  }
  incrementAsync() {
    store.dispatch(actions.incrementAsync(1))}decrementAsync() {
    store.dispatch(actions.decrementAsync(1))}render() {
    return (
      <div>
        <h1>{store.getState()}</h1>
        <button onClick={this.incrementAsync.bind(this)}>Asynchronous plus 1</button>
        <button onClick={this.decrementAsync.bind(this)}>Asynchronous minus 1</button>
      </div>)}}export default App
Copy the code

Eighteen, react – story

React redux allows you to use Redux in React.

React-redux automatically rerenders pages by listening for changes in data, instead of using store. Subscribe () to listen for changes in data and manually coding to render pages. Install first:

# If you use npm:
npm install react-redux

# Or if you use Yarn:
yarn add react-redux
Copy the code
  1. All of theUIComponents should wrap a container component, and they are parent-child.
  2. Container components are true andreduxInteractive and can be used freelyreduxtheapi.
  3. You cannot use any in UI componentsreduxtheapi.
  4. Container components pass to componentsreduxThe saved state and the method used to manipulate the state, throughpropsPass.

The connect() method is used to connect UI components to redux.

connect(mapStateToProps? , mapDispatchToProps? , mergeProps? , options?) Four parameters are received. Only the first two parameters are described here:

  1. MapStateToProps: Function, representing the state saved in redux, returns an object in which the key is the key passed to the UI component props, and the value is the value passed to the UI component props.

  2. MapDispatchToProps: the value of the Function | Object, indicates the operating status in a redux method, returns an Object, the key Object is passed to the UI components props key, the value is passed to the UI component value of props.

  3. MergeProps: Value is Function.

  4. Options: The value is Object.

Here’s a quick example of adding and subtracting using react-redux:

Create redux/store.js first:

// createStore is introduced to create the core store object in Redux
import { createStore, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'

// Define a constant value of type type
export const INCREMENT = 'increment' // 加1
export const DECREMENT = 'decrement' / / minus 1

/** * create action */
export const actions = {
  increment(data) {
    return { type: INCREMENT, data }
  },
  decrement(data) {
    return { type: DECREMENT, data }
  }
}

/** * Create reducer *@param {*} PreState Indicates the previous state *@param {*} Action Action object *@returns* /
function countReducer(preState = 0, action) {
  /** * Get type and data * type from the action object. The default initialization is similar to @@redux/ initc.q. 3. O.K.G a random value */
  const { type, data } = action
  switch (type) {
    case INCREMENT:
      return preState + data
    case DECREMENT:
      return preState - data
    default:
      return preState
  }
}
const store = createStore(countReducer, applyMiddleware(thunk))

export default store
Copy the code

Create a new Count container component containers/ count.jsx:

// Introduce the UI component of Count
import CountUI from '.. /components/Count'

/ / into actions
import { actions } from '.. /redux/store'

// Introduce connect to connect UI components to redux
import { connect } from 'react-redux'

/ * * *@param {*} State: State saved by redux. React-redux automatically passes */
const mapStateToProps = function (state) {
  return { count: state }
}

/ * * *@param {*} Dispatch redux's dispatch method, which is automatically passed */ by react-redux
const mapDispatchToProps = function (dispatch) {
  return {
    incr: (data) = > dispatch(actions.increment(data)),
    decr: (data) = > dispatch(actions.decrement(data))
  }
}

// Create and expose a Count container component with connect()()
export default connect(mapStateToProps, mapDispatchToProps)(CountUI)
Copy the code

Note that the short for mapDispatchToProps can also pass in an object, and react-redux will automatically call dispatch() for us:

const mapDispatchToProps = {
  incr: actions.increment,
  decr: actions.decrement
}
Copy the code

Then create a new UI component components/ count.jsx:

import React from 'react'
// Introduce store and Action
class Count extends React.Component {
  componentDidMount() {
    console.log(this.props) / / {store: {... }, count: 0, incr: ƒ, decr: ƒ}
  }
  increment() {
    console.log(this.props)
    this.props.incr(1)}decrement() {
    this.props.decr(1)}render() {
    return (
      <div>
        <h1>{this.props.count}</h1>
        <button onClick={this.increment.bind(this)}>Add 1</button>
        <button onClick={this.decrement.bind(this)}>Minus 1</button>
      </div>)}}export default Count
Copy the code

In app.js, the store needs to be passed to the container component via props:

import React from 'react'
/ / into the store
import store from './redux/store'

// Introduce the Count container
import Count from './containers/Count'
class App extends React.Component {
  render() {
    // Pass store to the container component
    return <Count store={store} />}}export default App
Copy the code

By using React-Redux, we write all the code that interacts with the Redux state into the container component, associating them with connect(), and finally the UI component only needs to be invoked via props.

18.1, the Provider

A Provider enables all components to receive a store and bind to the component as props.

If we want to use components many times, each component passes the store, as follows:

import store from './redux/store'
class App extends React.Component {
  render() {
    return (
      <div>
        <Count store={store} />
        <Count store={store} />
        <Count store={store} />
        <Count store={store} />
        <Count store={store} />
        <Count store={store} />
        <Count store={store} />
        <Count store={store} />
        <Count store={store} />
      </div>)}}Copy the code

You can pass store to the Provider, and store is automatically passed to the container component:

import { Provider } from 'react-redux'
import store from './redux/store'
import App from './App'
ReactDOM.render(
  <React.StrictMode>
    <BrowserRouter>
      <Provider store={store}>
        <App />
      </Provider>
    </BrowserRouter>
  </React.StrictMode>.document.getElementById('root'))Copy the code

The container component does not need to pass the store manually:

import store from './redux/store'
class App extends React.Component {
  render() {
    return (
      <div>
        <Count />
        <Count />
        <Count />
        <Count />
        <Count />
        <Count />
        <Count />
        <Count />
      </div>)}}Copy the code

18.2. Integrate UI components and container components

If there are 100 UI components in the project that need container components, then we need to create 100 container components. This will multiply the file. We can combine UI components and container components in one file:

// Container/Count.jsx

import { actions } from '.. /redux/store'
import { connect } from 'react-redux'
import React from 'react'

/ / UI components
class Count extends React.Component {
  componentDidMount() {
    console.log(this.props) / / {store: {... }, count: 0, incr: ƒ, decr: ƒ}
  }
  increment() {
    console.log(this.props)
    this.props.incr(1)}decrement() {
    this.props.decr(1)}render() {
    return (
      <div>
        <h1>{this.props.count}</h1>
        <button onClick={this.increment.bind(this)}>Add 1</button>
        <button onClick={this.decrement.bind(this)}>Minus 1</button>
      </div>)}}// Export the container component
export default connect(
  // mapStateToProps
  (state) = > {
    return { count: state }
  },
  // mapDispatchToProps
  {
    incr: actions.increment,
    decr: actions.decrement
  }
)(Count)
Copy the code

18.3. Data sharing between components

The redux features introduced earlier are relatively simple. The data in redux is just a single basic data type. The actual development will use a lot of data, we need to merge the redux data, we need to store the object {}. You can use the combineReducers() method to implement the merge:

/ / introduce combineReducers
import { combineReducers } from 'redux'

/** ** Summarize all reducers by using combineReducers() and merge the results returned by reducer * into a state object. {a: aReducer,b: bReducer} */
const allReducer = combineReducers({
  a: aReducer,
  b: bReducer
})
Copy the code

We also need a clearer division of functionality, as follows:

├ ─ ─ redux ├ ─ ─ the actions// All actions└ ─ ─ personAction. Js// Customize the Person Action└ ─ ─ bookAction. Js// Customize the book action├ ─ ─ reducers// All reducers└ ─ ─ index. JsCompile all reducer files└ ─ ─ countReducer. Js// Customize the person reducer└ ─ ─ bookReducer. Js// Customize the book reducer├ ─ ─ constant. Js/ / constant├ ─ ─ store. Js// store
Copy the code

Here is an example of how the Person and Book components implement that one Person can have more than one Book:

Write a constant

/** * type Constant value */
export const ADD_PERSON = 'addPerson' // Add a person
export const ADD_BOOK = 'addBook'     // Add a book
Copy the code

Write the Action:

// redux/actions/bookAction

import { ADD_BOOK } from '.. /constant'

export const addBookAction = (data) = > {
  return { type: ADD_BOOK, data }
}
Copy the code
// redux/actions/PersonAction

import { ADD_PERSON } from '.. /constant'

export const addPersonAction = (data) = > {
  return { type: ADD_PERSON, data }
}
Copy the code

Write the reducer:

// redux/reducers/bookReducer

import { ADD_BOOK } from '.. /constant.js'
export default function bookReducer(preState = [], action) {
  const { type, data } = action
  switch (type) {
    case ADD_BOOK:
      return [data, ...preState]
    default:
      return preState
  }
}
Copy the code
// redux/reducers/personReducer

import { ADD_PERSON } from '.. /constant'

// Initialize state
const initState = [{ id: '001'.name: 'Joe' }]

function personReducer(preState = initState, { type, data }) {
  switch (type) {
    case ADD_PERSON:
      return [data, ...preState]

    default:
      return preState
  }
}

export default personReducer
Copy the code
// redux/reducers/index.js

Compile all reducer */

import { combineReducers } from 'redux'

/ / introduce bookReducer
import bookReducer from './bookReducer'
/ / introduce personReducer
import personReducer from './personReducer'

/** ** Summarize all reducers by using combineReducers() and merge the results returned by reducer * into a state object. For example: {a: XXX,b: XXX} */
const allReducer = combineReducers({
  storeBook: bookReducer,
  storePerson: personReducer
})

export default allReducer
Copy the code

Write the store:

import { createStore, applyMiddleware } from 'redux'

import thunk from 'redux-thunk'
import allReducer from '.. /redux/reducers'
import { composeWithDevTools } from 'redux-devtools-extension'

const store = createStore(allReducer, composeWithDevTools(applyMiddleware(thunk)))

export default store
Copy the code

Write the Person and Book components:

import React from 'react'
import { connect } from 'react-redux'
import { addBookAction } from '.. /redux/actions/bookAction'
class Book extends React.Component {
  handleKeyUp(e) {
    if(e.keyCode ! = =13) return
    const val = e.target.value
    if(! val.trim() ===' ') return
    this.props.addBookAction({ id: Date.now(), name: val })
    e.target.value = ' '
  }
  render() {
    return (
      <div>
        <h1>I'm the Book component</h1>
        <input type="text" name="book" placeholder="Add a book" onKeyUp={this.handleKeyUp.bind(this)} />
      </div>)}}export default connect(
  (state) = > {
    return { storeBook: state.storeBook }
  },
  {
    addBookAction: addBookAction
  }
)(Book)
Copy the code
import React from 'react'
import { connect } from 'react-redux'
import { addPersonAction } from '.. /redux/actions/personAction'
class Person extends React.Component {
  render() {
    return (
      <div style={{ borderBottom: '1px solid #ccc' }}>
        <h1>I'm the Person component</h1>{this.props.storeperson [0].name} {this.props.storebook.map (({id, name}) => () {this.props.storeperson [0].name}<span key={id}>{name},</span>
        ))}
      </div>)}}export default connect(
  (state) = > {
    return { storeBook: state.storeBook, storePerson: state.storePerson }
  },
  {
    addPersonAction: addPersonAction
  }
)(Person)
Copy the code

Finally use it in app.js:

import React from 'react'
import Book from './components/Book'
import Person from './components/Person'
class App extends React.Component {
  render() {
    return (
      <div>
        <Person />
        <Book />
      </div>)}}export default App
Copy the code

18.4,reducerPure functions

The Reducer function of redux must be a pure function. As long as it is the same input (argument), it must get the same output (return).

  1. Parameter data cannot be overwritten.
  2. There are no side effects, such as network requests.
  3. You can’t useDate.now()orMath.random()Such impure methods.

Because the only way to compare whether all the properties in two javascript objects are exactly the same is to do a deep comparison. However, deep comparisons can be very costly in real applications and require a lot of comparisons, so an effective solution is to make a rule that the developer should return a new object regardless of any changes, and the developer should return the old object if there are no changes. This is why Redux designed reducer as a pure function.

18.5 redux developer tools

The Redux developer tool requires not only installing the Chrome plugin, but also configuring it in your project:

  1. Install the Chrome plug-in Redux DevTools

  2. Redux-devtools-extension: redux-devtools-extension:

/ / installation
npm install redux-devtools-extension
Copy the code
  1. And then in the projectstore.jsIn the configuration:
import { composeWithDevTools } from 'redux-devtools-extension'

const store = createStore(allReducer,composeWithDevTools( applyMiddleware(thunk) ))
Copy the code

In the 19th, Hook,

Hook is a new feature in React 16.8. It can use state and other React features without writing a class. Some commonly used hooks will be introduced below.

19.1, useState ()

State hooks allow function components to have State and read and write State data. Grammar:

const [xxx, setXXX] = React.useState(initValue)
Copy the code

InitValue is cached internally when it is initialized for the first time. The return value is an array of two elements, the first being the current value of the internal state and the second being the function that updates the state value.

SetXXX () can be written in two ways:

  1. setxxx(newValue): Parameter is non-functional value, directly specify the new state value, internal overwrite the original state value.
  2. setxxx(value=> newvalue)The: argument is a function that receives the previous status value and returns the new status value, internally overwriting the original status value.
import React, { useState } from 'react'

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

  function addCount() {
    // The first way
    setCount(count + 1)
    // The second way
    setCount((count) = > {
      return count + 1})}return (
    <div>
      <p>Count is: {count}</p>
      <button onClick={addCount}>Click on the + 2</button>
    </div>)}Copy the code

19.2, Effect ()

Effect Hooks can use lifecycle Hook functions in function components. UseEffect Hook can be regarded as a combination of componentDidMount, componentDidUpdate and componentWillUnmount.

Effect() passes in a callback function that executes after the first render and after each update:

import { useEffect } from 'react'
// componentDidMount and componentDidUpdate
useEffect(() = > {
  console.log('success')})Copy the code

Effect() can also be passed a second argument. It is an array specifying the dependencies to listen for. Rerender only when dependencies change. Default does not transmit means listens on all. An empty array [] means none is listened on.

const [count, setCount] = useState(0)
const [name, setName] = useState('Joe')

ComponentDidMount () and componentDidUpdate()
useEffect(() = > {
// TODO
}) // Do not transmit to monitor all

// componentDidMount()
useEffect(() = > {
// TODO
}, []) // [] Does not listen

// it is executed at initialization and when count changes
useEffect(() = > {
// TODO
}, [count]) // only listen on count
Copy the code

Effect() can return a function that is executed before the component is unloaded. Equivalent to the componentWillUnmount hook function:

import { useEffect } from 'react'
import ReactDOM from 'react-dom'
function Demo() {
  useEffect(() = > {
    return function () {
      console.log('Execute before component uninstallation')}})/ / unloading
  function handleUnmount() {
    ReactDOM.unmountComponentAtNode(document.getElementById('root'))}return (
    <div>
      <button onClick={handleUnmount}>Uninstall the component</button>
    </div>)}Copy the code

When you click uninstall the component, the function returned by Effect() is executed.

Here is an example of a timer:

import { useState, useEffect } from 'react'
import ReactDOM from 'react-dom'
// Timer component
function Counter() {
  const [count, setCount] = useState(0)
  useEffect(() = > {
    let timer = setInterval(() = > {
      setCount((count) = > count + 1)},1000)

    return () = > {
      clearInterval(timer) // Clear timers before uninstalling components}}, [])/ / unloading
  function handleUnmount() {
    ReactDOM.unmountComponentAtNode(document.getElementById('root'))}return (
    <div>
      <p>The value of count is {count}</p>
      <button onClick={handleUnmount}>Uninstall the component</button>
    </div>)}Copy the code

One thing to note about using Effect() ⚠ is that if there are multiple side effects, multiple useeffects () should be called instead of being written together:

import { useEffect, useState } from 'react'

function App() {
  const [timerA, setTimerA] = useState(0)
  const [timerB, setTimerB] = useState(0)
  useEffect(() = > {
    setInterval(() = > setTimerA((timerA) = > timerA + 1), 1000)
  }, [])

  useEffect(() = > {
    setInterval(() = > setTimerB((timerB) = > timerB + 1), 2000)}, [])return (
    <div>
      {timerA},{timerB}
    </div>)}Copy the code

19.3, useRef ()

Using String Ref, Callback Ref, or Create Ref in a function component throws an error. Because the function component has no instance, it cannot be used in the function component and useRef() is required.

What useRef does:

  1. To obtainDOMElement node.
  2. Gets an instance of the child component.
  3. Storage of shared data between render cycles.

Get the DOM element node:

import { useEffect, useRef } from 'react'

function App() {
  let inputRef = useRef()
  useEffect(() = > {
    console.log(inputRef.current) // <input type="text">
  }, [])

  return <input type="text" ref={inputRef} />
}
Copy the code

Because a function component has no instance, if you want to get an instance of a subcomponent using ref, the subcomponent group is written as a class component. Get instances of child components:

import React, { useEffect, useRef } from 'react'

function App() {
  let childRef = useRef()
  useEffect(() = > {
    childRef.current.handleChild() // Child Component
  }, [])

  return <Child ref={childRef} />
}

class Child extends React.Component {
  handleChild() {
    console.log('Child Component')}render() {
    return <div>The Child components</div>}}Copy the code

Storage of shared data between render cycles:

import React, { useEffect, useRef } from 'react'

function App() {
  const timerRef = useRef()
  useEffect(() = > {
    let timerId = setInterval(() = > {
      // TODO
    })
    timerRef.current = timerId
    return () = > {
      clearInterval(timerRef.current)
    }
  }, [])

  return <div>App.js</div>
}
Copy the code

The timer ID is stored in the useRef. The timer ID is available not only in useEffect, but also in the entire component function.

19.4, useContext ()

CreateContext () is a communication mode between components commonly used for grandparent component communication.

Suppose there are three components, A, B, and C, which are group grandchildren. A represents the parent component, B represents the child component, and C represents the grandchild component. The data of A component is transmitted to C component:

Use createContext() to pass cross-level component data:

import React from 'react'

// Create a Context container object outside the component
const MyContext = React.createContext()

/ / the parent component
class A extends React.Component {
  constructor(props) {
    super(props)
    this.state = { username: 'Joe'}}render() {
    return (
      <>
        <div>I'm component A</div>{/* Wrap the child component with myContext.provider and pass the data through value */}<MyContext.Provider value={{ username: this.state.username}} >
          <B />
        </MyContext.Provider>
      </>)}}/ / child component
class B extends React.Component {
  render() {
    return (
      <>
        <div>I'm component B</div>
        <C />
      </>)}}/ / sun components
class C extends React.Component {
  static contextType = MyContext // Declare to receive the context
  render() {
    const { username } = this.context / / get the context
    return <div>I am component C and THE name I get from component A is {username}.</div>}}Copy the code

If the C component is a function component, use Consume to get the value, and the value in the callback is the value of the context:

function C() {
  return <MyContext.Consumer>{(value) => <div>I am component C, and the name I get from component A is: {value.username}</div>}</MyContext.Consumer>
}
Copy the code

C components can also use useContext() to get values:

function C() {
  const { username } = useContext(MyContext)
  return <div>I am component C and THE name I get from component A is {username}.</div>
}
Copy the code

UseContext (MyContext) is equivalent to static contextType = MyContext or < myContext.consumer > in the class component.

19.5, useReducer

UseReducer is an alternative to useState. It is used in a similar way to Redux:

const [state, dispatch] = useReducer(reducer, initialArg, init);
Copy the code

UseReducer () can be passed three arguments:

  1. The first reducer parameter is used in much the same way as the Reducer function in Redux: (state, action) => newState.

  2. The second parameter, initialArg, is the initial value of the state.

  3. The third parameter init is an optional function for Lazy Initialization, which returns the state after Initialization.

Here is an example of a counter:

import { useReducer } from 'react'

/ / Reducer function
function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 }
    case 'decrement':
      return { count: state.count - 1 }
    default:
      throw new Error()}}function Counter() {
  const [state, dispatch] = useReducer(reducer, { count: 0 })
  return (
    <>
      <p>Count: {state.count}</p>
      <button onClick={()= > dispatch({ type: 'decrement' })}>-</button>
      <button onClick={()= > dispatch({ type: 'increment' })}>+</button>
    </>)}export default Counter
Copy the code

Use useReducer() in a variety of scenarios. Here are a few examples:

  • State is an array or object.

  • State changes can be complex, often requiring many states to be changed in a single operation.

  • Complex business logic, UI and business can be separated.

  • Different properties are bundled together and must be managed using a single State Object.

If there is a login function, use useState() and useReduer() respectively:

/ / useState () method

import { useState, useReducer } from 'react'

// Login component
function LoginUI() {
  const [name, setName] = useState(' ') / / user name
  const [pwd, setPwd] = useState(' ') / / password
  const [isLoading, setIsLoading] = useState(false) // Whether to display loading, sending a request
  const [error, setError] = useState(' ') // Error message
  const [isLoggedIn, setIsLoggedIn] = useState(false) // Whether to log in

  async function LoginPage() {
    setError(' ')
    setIsLoading(true)
    try {
      await loginService({ name, pwd })
      setIsLoggedIn(true)
      setIsLoading(false)}catch (err) {
      // Login failure: Error information is displayed, the user name and password are cleared, and the loading flag is cleared
      setError(error.message)
      setName(' ')
      setPwd(' ')
      setIsLoading(false)}}// Login interface
  function loginService() {
    // ...
  }

  return <h2>Login Page</h2>
}
Copy the code

As the code above uses useState(), you can see that as the requirements become more complex, more states are defined, more setState() calls are made, and it is easy to set errors or omissions, and maintainability is poor. Here’s how to use useReducer() :

/ / useReducer () method

import { useReducer } from 'react'

const initState = { name: ' '.pwd: ' '.error: ' '.isLoading: false.isLoggedIn: false }

function loginReducer(state, action) {
  switch (action.type) {
    case 'login':
      return { ...state, isLoading: true.error: ' ' }
    case 'success':
      return { ...state, isLoggedIn: true.isLoading: false }
    case 'error':
      return { ...state, error: action.payload.error, name: ' '.pwd: ' '.isLoading: false }
    default:
      return state
  }
}

// Login component
function LoginPage() {
  const [state, dispatch] = useReducer(loginReducer, initState)
  const { name, pwd, error } = state

  async function Login() {
    dispatch({ type: 'login' })
    try {
      await loginService({ name, pwd })
      dispatch({ type: 'success'})}catch (err) {
      // Login failure: Error information is displayed, the user name and password are cleared, and the loading flag is cleared
      dispatch({
        type: 'error'.payload: { error: error.message }
      })
    }
  }
  // Login interface
  function loginService() {
    // ...
  }

  return <h2>Login Page</h2>
}
export default Login
Copy the code

reference

Github.com/xzlaptt/Rea…

zhuanlan.zhihu.com/p/146773995

Segmentfault.com/a/119000002…

www.bilibili.com/video/BV1wy…

www.ruanyifeng.com/blog/2020/0…

Juejin. Cn/post / 684490…