Hook is a new feature in React 16.8. It lets you use state and other React features without having to write a class
In the past, functional components could not have their own state. They could only render their UI through props and context. In the business logic, some scenes must use state. Then we can only define functional components as class components. Now, with hooks, we can easily maintain our state in functional components without changing to class components.
React Hooks address the issue of state sharing, in which state logic reuse is only shared, not data sharing. We know that before React Hooks, we used higher-order components and render props for state reuse, so why did React developers introduce React Hooks when they already had them? What are the advantages of React Hook for higher-Order Components and render-props?
The React Hook example
Let’s take a look at the React Hook demo
import { useState } from 'React';
function Example() {
// Declare a new state variable, which we'll call "count"
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={()= > setCount(count + 1)}>
Click me
</button>
</div>
);
}
Copy the code
React Hook Is not used
class Example extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
render() {
return (
<div>
<p>You clicked {this.state.count} times</p>
<button onClick={()= > this.setState({ count: this.state.count + 1 })}>
Click me
</button>
</div>); }}Copy the code
In React Hook, the class Example component becomes a functional component. However, the functional component has its own state and can update its state. This is all thanks to the Hook useState, which returns a pair of values: the current state and a function that lets you update it, which you can call in an event handler or some other place. It is similar to the this.setState of the class component, but it does not merge the new state with the old state
React
A solution for reuse of state logic
Hook is another solution for reusing state logic. React developers have been constantly proposing and improving reusing state logic, from mixins to higher-order components to Render Props to current hooks. Let’s take a brief look at the previous solutions
Mixin
model
In the early days of React, it was proposed to reuse logic between components based on Mixin patterns. In Javascript, we can think of Mixin inheritance as a way to extend collection capabilities. Each new object we define has a stereotype from which it can inherit more properties. Stereotypes can be inherited from other objects, but more importantly, they can define properties for any number of objects. We can take advantage of this fact to promote functional reuse.
Mixins in React are mainly used in two completely unrelated components. A set of basically similar functions can be extracted and injected in the way of mixins, thus realizing code reuse. For example, if a component needs to be updated every once in a while in a different component, we can do this by creating a setInterval() function that needs to be uninstalled when the component is destroyed. So you can create a simple mixin that provides a simple setInterval() function that is automatically cleaned up when the component is destroyed.
var SetIntervalMixin = {
componentWillMount: function() {
this.intervals = [];
},
setInterval: function() {
this.intervals.push(setInterval.apply(null, arguments));
},
componentWillUnmount: function() { this.intervals.forEach(clearInterval); }}; var createReactClass = require('create-React-class');
var TickTock = createReactClass({
mixins: [SetIntervalMixin], // 使用 mixin
getInitialState: function() {
return {seconds: 0};
},
componentDidMount: function() { this.setInterval(this.tick, 1000); // Call the mixin method}, tick:function() {
this.setState({seconds: this.state.seconds + 1});
},
render: function() {
return (
<p>
React has been running for{this.state.seconds} seconds. </p> ); }}); ReactDOM.render( <TickTock />, document.getElementById('example'));Copy the code
mixin
The disadvantages of the
- different
mixin
They may depend on each other and have strong coupling, resulting in high maintenance costs mixin
May have conflicting names and cannot use the same namemixin
mixin
Even if they start out simple, they snowball in complexity over time as business scenarios multiply
Mixins are a scourge
React does not recommend the use of mixin mode for code reuse. React fully recommends the use of higher-order components instead of mixin mode. Meanwhile, ES6 does not include any mixin support. Therefore, when you use ES6 classes in React, mixins are not supported.
High order component
HOC is an advanced technique used in React to reuse component logic. HOC itself is not part of the React API; it is a design pattern based on the composite features of React
Advanced components are not the React API, but a technique for implementing React. Advanced components can be seen as implementations of the Decorator Pattern in React. Decorator pattern: To dynamically attach responsibilities to objects, decorators provide a more flexible alternative to inheritance to extend functionality.
Specifically, a higher-order component is a function that takes a component and returns a new component.
A component converts props to UI, and a higher-order component converts a component to another component
We can dynamically add log printing to other components from higher-order components without affecting the functionality of the original component
function logProps(WrappedComponent) {
return class extends React.Component {
componentWillReceiveProps(nextProps) {
console.log('Current props: ', this.props);
console.log('Next props: ', nextProps);
}
render() {
return<WrappedComponent {... this.props} />; }}}Copy the code
Render Propss
The term “Render Props” refers to a simple technique for sharing code between React components using a prop with a value of function
A component with Render Props accepts a function that returns a React element and calls it instead of implementing its own Render logic
Below we provide a
component with prop that dynamically determines what needs to be rendered so that the logic and state of the
component can be reused without changing its rendering structure.
class Cat extends React.Component {
render() {
const mouse = this.props.mouse;
return (
<img src="/cat.jpg" style={{ position: 'absolute', left: mouse.x, top: mouse.y }} />
);
}
}
class Mouse extends React.Component {
constructor(props) {
super(props);
this.handleMouseMove = this.handleMouseMove.bind(this);
this.state = { x: 0, y: 0 };
}
handleMouseMove(event) {
this.setState({
x: event.clientX,
y: event.clientY
});
}
render() {
return (
<div style={{ height: '100%' }} onMouseMove={this.handleMouseMove}>
{this.props.render(this.state)}
</div>
);
}
}
class MouseTracker extends React.Component {
render() {
return(<div> <h1> move mouse! </h1> <Mouse render={mouse => ( )}/> </div> ); }}Copy the code
However, usually we say Render Props because the mode is called Render Props, not because we have to name a prop with Render. We could also write it this way
<Mouse>
{mouse => (
<Cat mouse={mouse} />
)}
</Mouse>
Copy the code
The React Hook motivation
React Hook is another new solution proposed by the official website. Before getting to know React Hook, let’s take a look at the motivation proposed by React Hook
- Reusing state logic between components is difficult
- Complex components become difficult to understand
- incomprehensible
class
Here’s what I understand about these three motivations:
Reusing state logic between Components is difficult. Previously, we solved the problem of reusing state logic with higher-order Components and Render Propss. Many libraries use these patterns to reuse state logic, such as Redux and React Router. Higher-order components and render properties are nested layer by layer through composition of common components, which greatly increases the level of our code, resulting in excessive nesting of layers. React devTool clearly shows the degree of hierarchy nesting caused by using these two modes
Complex components become difficult to understand, and in the context of changing business requirements, components are increasingly overwhelmed with state logic and side effects, with each lifecycle often containing unrelated logic. We usually write code according to the rule of function singleness. A function usually does one thing, but in a lifecycle hook function it usually does many things at once. For example, when we need to make an Ajax request to get data in componentDidMount, and sometimes we write event bindings in this lifecycle, Sometimes even need the data in the componentWillReceiveProps like componentDidMount processing.
Code that is related and needs to be modified against each other is split, while completely unrelated code is grouped together in the same method. This is very buggy and leads to logical inconsistencies.
The class component can be used as a component of the class. If you understand the binding problem of this class, it is not difficult to get started. Just to be clear, this isn’t just React behavior; This has to do with how JavaScript functions work. So as long as you understand how JS functions work, this binding is not a problem. It’s just that sometimes we write a lot of code to bind this to make sure that this is pointing correctly, and if we forget to bind this, we get all kinds of bugs. Bind this method:
1.this.handleClick = this.handleClick.bind(this);
2.<button onClick={(e) => this.handleClick(e)}>
Click me
</button>
Copy the code
React Hook was put forward to solve the above problems
State the hooks used
Let’s go back to the code and see how to define state in a functional component
import React, { useState } from 'React';
const [count, setCount] = useState(0);
Copy the code
-
UseState did what
We can see that in this function, we define a ‘state variable ‘through useState, which provides exactly the same functionality as this.state in class. Equivalent to the following code
class Example extends React.Component { constructor(props) { super(props); this.state = { count: 0 }; } Copy the code
-
UseState parameters
In the code, we pass 0 as the useState argument, whose value will be taken as the initial count value. Of course, this argument is not limited to passing numbers and strings. You can pass an object as the initial state. If state needs to store the values of multiple variables, call useState multiple times
-
UseState return value
This is similar to this.state.count and this.setstate in class, except that you need to fetch them in pairs. It’s easy to see from [count, setCount] that this is ES6’s way of writing a deconstructed array. Equivalent to the following code
let _useState = useState(0);// Returns an array of two elements let count = _useState[0];// First value in array let setCount = _useState[1];// Second value in the array Copy the code
Read status value
Just use variables
Before writing
<p>You clicked {this.state.count} times</p>
Copy the code
Now write
<p>You clicked {count} times</p>
Copy the code
Update the status
Update with setCount function
Before writing
<button onClick={() => this.setState({ count: this.state.count + 1 })}>
Click me
</button>
Copy the code
Now write
<button onClick={() => setCount(count + 1)}>
Click me
</button>
Copy the code
Here setCount takes the new state value that has been modified
Declare multiple state variables
State hooks can be used multiple times in a component to declare multiple state variables
function ExampleWithManyStates() {
// Declare multiple state variables!
const [age, setAge] = useState(42);
const [fruit, setFruit] = useState('banana');
const [todos, setTodos] = useState([{ text: 'Learn Hooks' }]);
// ...
}
Copy the code
React suppose that when you call it multiple timesuseState
You can guarantee that they will be called in the same order every time you render
Why does React dictate that the call order remain the same every time it renders them? This is a crucial question to understand hooks
Rules of the Hook
A Hook is essentially a JavaScript function, but there are two rules to follow when using it. React requires that these two rules be enforced, otherwise exceptions will occur
- Use only at the top level
Hook
Never call hooks in loops, conditions, or nested functions. Make sure you always call them at the top of your React function
- Only in the
React
Call from a functionHook
Never call a Hook in a normal JavaScript function
The reason for these two rules is that you can use multiple State or Effect hooks in a single component. React relies on the order of Hook calls to know which State corresponds to which useState
function Form() {
const [name1, setName1] = useState('Arzh1');
const [name2, setName2] = useState('Arzh2');
const [name3, setName3] = useState('Arzh3');
// ...
}
// ------------
// First render
// ------------
useState('Arzh1') // 1. Initialize the state of the variable named name1 with 'Arzh1'
useState('Arzh2') // 2. Initialize state named name2 with 'Arzh2'
useEffect('Arzh3') // 3. Initialize state named name3 with 'Arzh3'
// -------------
// Secondary render
// -------------
useState('Arzh1') // 1. Read the state of the variable named name1 (argument ignored)
useState('Arzh2') // 2. Read the state of the variable named name2 (argument ignored)
useEffect('Arzh3') // 3. Read state named name3 (argument ignored)
Copy the code
If we violate React rules, use conditional rendering
if(name ! = =' ') {
const [name2, setName2] = useState('Arzh2');
}
Copy the code
Suppose the first time (name! When == “) is true, execute this Hook, and render the second time (name! If == ”) is false, do not execute this Hook, then the order of Hook calls will change, causing a bug
useState('Arzh1') // 1. Read the state of the variable name1
//useState('Arzh2') // 2. Hook ignored
useEffect('Arzh3') // 3. Read the state of variable named name2(previously name3)
Copy the code
React does not know what the second useState Hook should return. React would think that the second Hook call in this component would correspond to Arzh2’s useState as in the last render, but it doesn’t. So that’s why React enforces Hook use to follow these two rules, and we can use eslint-plugin-react-hooks to enforce constraints
Effect the hooks used
We added the use of Effect Hooks to the code above, added side effects to functional components, and changed the title of the page
useEffect((a)= > {
document.title = `You clicked ${count} times`;
});
Copy the code
If you’re familiar with React class lifecycle functions, you can think of useEffect Hook as componentDidMount, componentDidUpdate, and componentWillUnmount.
The three health hook functions can be replaced by useEffect
Let’s take a look at the scenarios that typically require side effects, such as sending requests, manually changing the DOM, logging, and so on. We usually call our side effects after the first DOM rendering and subsequent DOM updates. We can look at previous life cycle implementations
componentDidMount() {
document.title = `You clicked The ${this.state.count} times`;
}
componentDidUpdate() {
document.title = `You clicked The ${this.state.count} times`;
}
Copy the code
This is one of the second problems with the React Hook motivation we mentioned above, requiring the same code to be called in the first render as well as in subsequent ones
By default, Effect is executed after the first rendering and after every update, so we don’t need to think about whether It’s componentDidMount or componentDidUpdate, just understand that Effect is executed after the component is rendered
Clearance side effect
Sometimes there are side effects that need to be removed. For example, when we have a requirement to poll the server for the latest status, we need to remove the polling operation during uninstallation.
componentDidMount() {
this.pollingNewStatus()
}
componentWillUnmount() {
this.unPollingNewStatus()
}
Copy the code
We can use Effect to remove these side effects by simply returning a function in Effect
useEffect((a)= > {
pollingNewStatus()
// Tell React to run cleanup() before each render
return function cleanup() {
unPollingNewStatus()
};
});
Copy the code
The obvious difference is that useEffect is actually done before every render: cleanup(), while componentWillUnmount is only done once.
Effect performance optimization
UseEffect actually executes every update and can cause performance problems in some cases. We can optimize performance by skipping Effect. In the Class component, we can solve this by adding comparison logic to prevProps or prevState in componentDidUpdate
componentDidUpdate(prevProps, prevState) {
if(prevState.count ! = =this.state.count) {
document.title = `You clicked The ${this.state.count} times`; }}Copy the code
In Effect, we can simply add the second parameter to Effect and skip the update if there is no change
useEffect((a)= > {
document.title = `You clicked ${count} times`;
}, [count]); // Update only when count changes
Copy the code
Other Hooks
Due to space reasons, I will not expand this, interested can view their official website
Refer to the article
- Learn React Hooks in 30 minutes
- The React hooks practice
- From Mixin to HOC to Hook