React setState event bus controlled component

React setState event bus controlled component

  • Reference: Teacher Wang Hongyuan's React

setState 

  • During development, we cannot directly update the interface by modifying the value of state:
    • After modifying the state, we want React to re render the interface according to the latest state, but React does not know that the data has changed;
    • React does not implement an object similar to that in Vue2 Define property or Proxy in Vue3 to monitor data changes;
    • We must inform React that the data has changed through setState;
  • setState source code:

setState asynchronous update

  • Is the update of setState asynchronous?  
    • The final print result is Hello World;  
    • It can be seen that setState is an asynchronous operation. We can't get the latest state results immediately after executing setState
  • reason:
    • setState is designed to be asynchronous, which can significantly improve performance;
      • If the setState is updated every time it is called, it means that the render function will be called frequently and the interface will be re rendered, which is very inefficient;
      • The best way is to get multiple updates and then update them in batches;
    • If the state is updated synchronously, but the render function has not been executed, the state and props cannot be synchronized;
      • state and props are not consistent, which will cause many problems in development;

How to get asynchronous results

  • Method 1: callback of setState
    • setState accepts two parameters: the second parameter is a callback function, which will be executed after updating;
    • The format is as follows: setState(partialState, callback)

  • Mode 2: in the life cycle function:

Must setState be asynchronous?

  • Update in setTimeout:

  • Native DOM events

  • There are actually two situations:
    • In component life cycle or React composition events, setState is asynchronous;
    • In setTimeout or native dom events, setState is synchronous;
  • Consolidation of data

Merging of multiple state s

  • If an object is passed in, it will be merged and will not be executed multiple times. When a function is passed in, it will be executed multiple times.

 

shouldComponentUpdate

  • React provides us with a life cycle method shouldComponentUpdate (often referred to as SCU). This method accepts parameters and needs a return value:
  • The method has two parameters:
    • Parameter 1: the latest props attribute after nextProps is modified
    • Parameter 2: the latest state attribute after the nextState is modified
  • The return value of this method is a boolean type
    • If the return value is true, you need to call the render method;
    • If the return value is false, the render method does not need to be called for a long time;
    • The default return is true, that is, whenever the state changes, the render method will be called;
  • For example, we add a message attribute to the App:
    • jsx does not rely on this message, so its change should not cause re rendering;
    • However, because render listens to the change of state, it will render again, so the render method is finally called again;

PureComponent

  • If we need to manually implement shouldComponentUpdate for all classes, it will add a lot of workload to our developers.
    • Let's imagine the purpose of various judgments in shouldComponentUpdate?
    • Whether the data in props or state has changed determines whether shouldComponentUpdate returns true or false;
  • In fact, React has taken this into account, so React has been implemented for us by default. How to implement it?
    • Inherit class from PureComponent.

High order component memo

  • We need to use a high-order component, memo:
    • We wrap the previous Header, Banner and ProductList through the memo function;
    • Footer does not use the memo function for wrapping;
    • The final effect is that when the counter changes, the functions of Header, Banner and ProductList will not be re executed, but the functions of Footer will be re executed;
  • Example:
import React, { PureComponent, memo } from 'react';

// Header
const MemoHeader = memo(function Header() {
  console.log("Header Called");
  return <h2>I am Header assembly</h2>
})


// Main
class Banner extends PureComponent {
  render() {
    console.log("Banner render Function called");
    return <h3>I am Banner assembly</h3>
  }
}

const MemoProductList = memo(function ProductList() {
  console.log("ProductList Called");
  return (
    <ul>
      <li>Item list 1</li>
      <li>Product list 2</li>
      <li>Product list 3</li>
      <li>Product list 4</li>
      <li>Product list 5</li>
    </ul>
  )
})

class Main extends PureComponent {
  render() {
    console.log("Main render Function called");
    return (
      <div>
        <Banner/>
        <MemoProductList/>
      </div>
    )
  }
}

// Footer
const MemoFooter = memo(function Footer() {
  console.log("Footer Called");
  return <h2>I am Footer assembly</h2>
})


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

    this.state = {
      counter: 0
    }
  }

  render() {
    console.log("App render Function called");
    return (
      <div>
        <h2>Current count: {this.state.counter}</h2>
        <button onClick={e => this.increment()}>+1</button>
        <MemoHeader/>
        <Main/>
        <MemoFooter/>
      </div>
    )
  }

  increment() {
    this.setState({
      counter: this.state.counter + 1
    })
  }
}

setState immutable power

import React, { PureComponent } from 'react';

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

    // reference type
    this.state = {
      friends: [
        { name: "lilei", age: 20 },
        { name: "lily", age: 25 },
        { name: "lucy", age: 22 }
      ]
    }
  }

  // shouldComponentUpdate(newProps, newState) {
  //   if (newState.friends !== this.state.friends) {
  //     return true;
  //   }

  //   return false;
  // }

  render() {
    return (
      <div>
        <h2>Friends list:</h2>
        <ul>
          {
            this.state.friends.map((item, index) => {
              return (
                <li key={item.name}>
                  full name: {item.name} 
                  Age: {item.age}
                  <button onClick={e => this.incrementAge(index)}>age+1</button>
                </li>
              )
            })
          }
        </ul>
        <button onClick={e => this.insertData()}>Add data</button>
      </div>
    )
  }

  insertData() {
    // 1. Don't do this in development
    // const newData = {name: "tom", age: 30}
    // this.state.friends.push(newData);
    // this.setState({
    //   friends: this.state.friends
    // });

    // 2. Recommended practices
    const newFriends = [...this.state.friends];
    newFriends.push({ name: "tom", age: 30 });
    this.setState({
      friends: newFriends
    })
  }

  incrementAge(index) {
    const newFriends = [...this.state.friends];
    newFriends[index].age += 1;
    this.setState({
      friends: newFriends
    })
  }
}

Event bus

  • Previously, data sharing is mainly realized through Context, but what should be done if there is event transmission between cross components in development?
    • In Vue, we can quickly implement an event bus through the instance of Vue to complete the operation;
    • In React, we can rely on a more used library events to complete the corresponding operations;
  • We can install events through npm or yarn:
    • yarn add events
  • Common API s for events:
    • Create EventEmitter object: eventBus object;
    • Issue event: eventbus Emit ("event name", parameter list);
    • Listening event: eventbus AddListener ("event name", listening function);
    • Remove event: eventbus Removelistener ("event name", listening function);
  • Example:
import React, { PureComponent } from 'react';
import { EventEmitter } from 'events';

// Event bus: event bus
const eventBus = new EventEmitter();

class Home extends PureComponent {
  componentDidMount() {
    eventBus.addListener("sayHello", this.handleSayHelloListener);
  }

  componentWillUnmount() {
    eventBus.removeListener("sayHello", this.handleSayHelloListener);
  }

  handleSayHelloListener(num, message) {
    console.log(num, message);
  }

  render() {
    return (
      <div>
        Home
      </div>
    )
  }
}

class Profile extends PureComponent {
  render() {
    return (
      <div>
        Profile
        <button onClick={e => this.emmitEvent()}>Click profile Button</button>
      </div>
    )
  }

  emmitEvent() {
    eventBus.emit("sayHello", 123, "Hello Home");
  }
}

export default class App extends PureComponent {
  render() {
    return (
      <div>
        <Home />
        <Profile />
      </div>
    )
  }
}

How to use ref

  • How to create refs to get the corresponding DOM? There are three ways:
  • Method 1: incoming string
    • Use this refs. Get the corresponding element in the incoming string format;
  • Method 2: pass in an object
    • The object is through react Created by createref();
    • When using, the created object is obtained, in which a current attribute is the corresponding element;
  • Method 3: pass in a function
    • This function will call back when the DOM is mounted. This function will pass in an element object, which we can save ourselves;
    • When using, you can directly get the previously saved element object
  • Example:
export default class App extends PureComponent {

  constructor(props) {
    super(props);

    this.titleRef = createRef();
    this.counterRef = createRef();
    this.titleEl = null;
  }

  render() {
    return (
      <div>
        {/* <h2 ref=String / object / function > Hello react</h2> */}
        <h2 ref="titleRef">Hello React</h2>
        {/* Current methods recommended by React */}
        <h2 ref={this.titleRef}>Hello React</h2>
        <h2 ref={arg => this.titleEl = arg}>Hello React</h2>
        <button onClick={e => this.changeText()}>Change text</button>
        <hr/>
        <Counter ref={this.counterRef}/>
        <button onClick={e => this.appBtnClick()}>App Button</button>
      </div>
    )
  }

  changeText() {
    // 1. Usage method 1: string (not recommended, subsequent updates will be deleted)
    this.refs.titleRef.innerHTML = "Hello Coderwhy";
    // 2. Usage mode 2: object mode
    this.titleRef.current.innerHTML = "Hello JavaScript";
    // 3. Usage mode 3: callback function mode
    this.titleEl.innerHTML = "Hello TypeScript";
  }

  appBtnClick() {
    this.counterRef.current.increment();
  }
}

Type of ref

  • The value of ref varies according to the type of node:
    • When the ref attribute is used for HTML elements, react. Is used in the constructor The ref created by createref() receives the underlying DOM element as its current attribute;
    • When the ref attribute is used to customize a class component, the ref object receives the mounted instance of the component as its current attribute;
    • You cannot use the ref attribute on function components because they have no instances;
  • Functional components have no instances, so their instances cannot be obtained through ref:
    • But sometimes, we may want to get a DOM element in a functional component;
    • At this time, we can use react Forwardref, we will also learn how to use ref in hooks later;

Controlled components

  • In React, HTML forms are handled differently from ordinary DOM elements: form elements are usually saved in some internal state s.
  • For example, the following HTML form elements:
    • This processing method is the DOM's default behavior of processing HTML forms. When the user clicks submit, it will be submitted to a server and the page will be refreshed;
    • In React, this behavior is not prohibited, but it is still valid;
    • However, JavaScript functions are usually used to facilitate form submission and access the form data filled in by users;
    • The standard way to achieve this effect is to use "controlled components";

  • 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 handleUsernameChange executes and updates the state of React every time you press a key, the displayed value will be updated with user input.

  • Example 1:
export default class App extends PureComponent {
  constructor(props) {
    super(props);

    this.state = {
      username: ""
    }
  }

  render() {
    return (
      <div>
        <form onSubmit={e => this.handleSubmit(e)}>
          <label htmlFor="username">
            user: 
            {/* Controlled components */}
            <input type="text" 
                   id="username" 
                   onChange={e => this.handleChange(e)}
                   value={this.state.username}/>
          </label>
          <input type="submit" value="Submit"/>
        </form>
      </div>
    )
  }

  handleSubmit(event) {
    event.preventDefault();
    console.log(this.state.username);
  }

  handleChange(event) {
    this.setState({
      username: event.target.value
    })
  }
}
  • Example 2:

  • Example 3:
export default class App extends PureComponent {
  constructor(props) {
    super(props);

    this.state = {
      username: "",
      password: "",
      valid: ""
    }
  }

  render() {
    return (
      <div>
        <form onSubmit={e => this.handleSubmit(e)}>
          <label htmlFor="username">
            user: 
            <input type="text" 
                   id="username"
                   name="username" 
                   onChange={e => this.handleChange(e)}
                   value={this.state.username}/>
          </label>
          <br/>
          <label htmlFor="password">
            password: 
            <input type="text" 
                   id="password" 
                   name="password" 
                   onChange={e => this.handleChange(e)}
                   value={this.state.password}/>
          </label>
          <br/>
          <label htmlFor="valid">
            verification: 
            <input type="text" 
                   id="valid" 
                   name="valid" 
                   onChange={e => this.handleChange(e)}
                   value={this.state.valid}/>
          </label>
          <br/>
          <input type="submit" value="Submit"/>
        </form>
      </div>
    )
  }

  handleSubmit(event) {
    event.preventDefault();
    const {username, password, valid} = this.state;
    console.log(username, password, valid);
  }

  handleChange(event) {
    this.setState({
      // Calculated attribute name
      [event.target.name]: event.target.value
    })
  }
}

Uncontrolled components

  • React recommends using controlled components to process form data in most cases:
    • In a controlled component, form data is managed by React component;
    • Another alternative is to use uncontrolled components, in which case the form data will be handled by the DOM node;
  • If we want to use the data in the uncontrolled component, we need to use ref to get the form data from the DOM node.
    • Let's do a simple exercise:
    • Use ref to get the input element;
  • In uncontrolled components, defaultValue is usually used to set the default value;
export default class App extends PureComponent {
  constructor(props) {
    super(props);

    this.usernameRef = createRef();
  }

  render() {
    return (
      <div>
        <form onSubmit={e => this.handleSubmit(e)}>
          <label htmlFor="username">
            user: 
            <input type="text" id="username" ref={this.usernameRef}/>
          </label>
          <input type="submit" value="Submit"/>
        </form>
      </div>
    )
  }

  handleSubmit(event) {
    event.preventDefault();
    console.log(this.usernameRef.current.value);
  }
}

Keywords: Front-end React

Added by AjithTV on Fri, 21 Jan 2022 21:28:32 +0200