After thinking for half an hour, I really didn’t know how to write at the beginning, so I filled it with some content to make it less empty.
After all, React is a lot of front-end cutters, and a deeper understanding of its features will help us eat better. When I relearned React, I found some interesting behaviors. I will summarize them and see if they resonate with people.
(The full text is only the author’s personal understanding and summary, do not spray if you do not like)
setState
Intermediate state
Sometimes we need to update the state after the component has been updated (the componentDidUpdate hook)! So if I update the status in the componentDidUpdate hook, does the intermediate state show up, that is, does the page flash?
class State extends PureComponent {
state = { count: 0 };
componentDidMount() {
this.setState({
count: 1
});
}
componentDidUpdate() {
console.log(document.getElementById('count').innerHTML);
if (this.state.count < 50) {
this.setState({ state: this.state.count + 1 });
}
}
render() {
return (
<span id="count">{ this.state.count }</span>); }}Copy the code
After running, you can see the result. The final state 50 is displayed on the page directly. The intermediate state 0-49 does not flash on the page (the page does not flash), but 1-49 is printed in sequence in the console.
ComponentDidUpdate, componentDidMount and other commit hooks will change the DOM tree, the browser only renders the final state, the intermediate state is not displayed.
I guess the reason is that the JS engine and the rendering engine share the same thread. A wave of setState operation in the middle is actually the JS engine occupying the thread, and the rendering engine cannot render, so the middle state does not appear (similar to the suspended state caused by the large amount of JS computation tasks).
Synchronous or asynchronous setState
Look at the following example
class State extends PureComponent {
state = { count: 0 }
componentDidMount() {
this.setState({ count: this.state.count + 1 });
console.log(this.state.count); / / 0
setTimeout((a)= > {
this.setState({ count: this.state.count + 1 });
console.log(this.state.count); / / 2
}, 0);
}
render() {
return (
<span id="count">{ this.state.count }</span>); }}Copy the code
When you look at the final output, you see that the first setState is not executed immediately, while the second setState is executed immediately.
First, the first setState is asynchronous, which is a batch update optimization done inside React. When a React event (such as onClick, a react composite event) is triggered, a flag: isBatchingUpdates is set to true. React stores the collected setState and updates it.
Secondly, according to the above, the second setState is called in setTimeout, and the React interaction event is not triggered. Therefore, batch updates are not performed, which is in the form of synchronization.
Of course, you can use the reactdom. unstable_batchedUpdates method to perform batch updates in asynchronous callbacks such as setTimeout:
class State extends PureComponent {
state = { count: 0 }
componentDidMount() {
setTimeout((a)= > {
ReactDom.unstable_batchedUpdates((a)= > {
this.setState({ count: this.state.count + 1 });
console.log(this.state.count); / / 0
this.setState({ count: this.state.count + 1 });
console.log(this.state.count); / / 0
});
console.log(this.state.count); / / 2
}, 0);
}
render() {
return (
<span id="count">{ this.state.count }</span>); }}Copy the code
Continuous updating
We now know that setState in the interactive event callback is asynchronous, so there are some problems with continuous updates:
class State extends PureComponent {
state = { count: 0 }
handleClick = (a)= > {
this.setState({ count: this.state.count + 1 });
this.updateSomething();
}
updateSomething = (a)= > {
this.setState({ count: this.state.count + 1 });
}
render() {
return (
<React.Fragment>
<span id="count">{ this.state.count }</span>{/* Actually click the button once, count += 1 */}<button onClick={ this.handleClick} >click me</button>
</React.Fragment>); }}Copy the code
We expect: Click on the Click me button, count += 2.
Actual result: count += 1.
Unfortunately, when we setState, setState is asynchronous, multiple setstates will be merged, which is equivalent to launching setState only once.
For operations like this that depend on the previous setState value, you can use the setState method of another method:
class State extends PureComponent {
state = { count: 0 }
handleClick = (a)= > {
this.setState(state= > ({ count: state.count + 1 }));
this.updateSomething();
}
updateSomething = (a)= > {
this.setState(state= > ({ count: state.count + 1 }));
}
render() {
return (
<React.Fragment>
<span id="count">{ this.state.count }</span>{/* Actually click the button once, count += 2 */}<button onClick={ this.handleClick} >click me</button>
</React.Fragment>); }}Copy the code
key
Key is an auxiliary identifier that helps React determine which elements are changed, added, and removed, but I found that we basically only use key in list rendering, as follows:
render() {
return (
<Fragment>{
list.map(item => (
<ListItem key={ item.id } { . item} / >))}</Fragment>
);
}
Copy the code
In fact, when dealing with component-derived state, using key can reduce our state reset operations:
The parent component
class Key extends Component {
state = {
activeItem: {
id: 0.name: 'listItem0'
}
}
render() {
const { activeItem = {} } = this.state;
return (
<div>
<ItemDetail
key={ activeItem.id }
itemData={ activeItem} / >
</div>
);
}
}
export default Key;
Copy the code
Child components
class ItemDetail extends PureComponent {
constructor(props) {
super(props);
this.state = {
name: `${ props.itemData.name }_child` // Derived state, which is bad behavior and should be avoided
}
}
fetchItemDetail = (a)= > {
// ajax request
}
componentDidMount() {
this.fetchItemDetail();
}
render() {
return (
<div>
{ /* item info */ }
</div>); }}Copy the code
In the example above, whenever the activeItem changes, the key passed by the parent component to the ItemDetail child component will change. Then the ItemDetail component will be deleted by React and rebuilt. Therefore, the child component will automatically generate the new derived state and obtain the new item details.
The advantage of this approach is that you don’t have to listen for changes in the props inside the child component to get new data and generate new derived state. The disadvantage is that each rendering of the key needs to remove the original component and regenerate it into a new component.
If the state inside the child component is complex and the diFF cost is high, consider using the above method to reset the state.
Ref
We have long been taught that using a REF to call a component instance method breaks the lifecycle of the component and should be avoided, but in some cases it is a must. Here’s a summary of how to get component instances in several cases:
Gets an instance of a higher-order component
We can pass through ref as a normal props property using the forwardRed method provided by React
import React, { forwardRef } from 'react';
import Child from './Child';
const ForwardRefChild = forwardRef((props, ref) = > {
return (
<Child
{ . props }
forwardedRef={ ref} / >
);
});
Copy the code
The parent component
class Ref extends PureComponent {
getRef = instance= > {
console.log(instance);
}
render() {
return (
<ForwardRefChild ref={ this.getRef} ></ForwardRefChild>); }}Copy the code
Child components
class Child extends PureComponent {
render() {
const { forwardedRef } = this.props;
return (
<div ref={ forwardedRef} ></div>); }}Copy the code
Gets a component instance decorated with the Connect decorator of React-Redux
The connect function has four inputs, of which the fourth is an optional parameter of type object and the object attributes include
- pure: Boolean - withRef: Boolean, indicating whether to save a reference to the contained component instance. The modified reference is obtained through the getWrappedInstance methodCopy the code
That’s right, you can get an instance through the getWrappedInstance method simply by setting withRef to true
class Ref extends PureComponent {
getRef = decoratedInstance= > {
console.log(decoratedInstance.getWrappedInstance()); // real instance
}
render() {
retrun (
<ChildDecoratedByConnect ref={ this.getRef} / >); }}Copy the code
Get the ref for props. Children
If we want to get the props. Children property from the component, we need to insert the props. Children property as follows:
class Child extends PureComponent {
getRef = node= > {
console.log(node); // Happy and happy
}
getChild = (a)= > {
const { children } = this.props;
const element = React.Children.toArray(children)[0]; // Get the children first child
// clone the child element
return React.cloneElement(element, {
ref: this.getRef / / rewrite the ref
});
}
render() {
return (
<div>{ this.getChild() }</div>); }}Copy the code
Instead of rendering props. Children directly, we made a copy with React. Clone and modified the ref value to achieve our goal.
In the example above, there is a problem that the ref of props. Children cannot be used because the Child component overwrites the ref of props. To solve this problem, we can combine the forwardRef mentioned above and pass the instance to the old ref
class Child extends PureComponent {
getRef = node= > {
const { forwardedRef } = this.props;
// function type ref
if ((typeof forwardedRef).toUpperCase() === 'FUNCTION') {
forwardedRef(node);
} else {
// Use the react. createRef generated ref object
forwardedRef.current = node;
}
console.log(node); // Happy and happy
}
getChild = (a)= > {
const { children } = this.props;
const element = React.Children.toArray(children)[0];
return React.cloneElement(element, {
ref: this.getRef
});
}
render() {
return (
<div>{ this.getChild() }</div>); }}Copy the code
In the child’s getRef function, assigning an instance to the original ref object or calling ref as an argument makes the instance available to the outer functions.
At the end
The above content, if any mistakes, welcome to correct.
Ok, it’s time to work!!
@Author: PaperCrane