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;
- setState is designed to be asynchronous, which can significantly improve performance;
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); } }