I’ve been studying things related to React recently. React’s component-based coding approach makes writing interfaces a lot easier. No wonder Flutter and Compose are embracing this approach. By the way, I also picked up the long-abandoned JS, which has become like a new language after several years of updates. It also supports the class syntax, which makes it easier for those familiar with object-oriented development to get started. However, this is always annoying and changeable. When we first write components using classes, we often encounter the problem that the object can not be found for no reason, and finally find that we need to bind(this).

There is also the problem that a lot of complex scenes can only be implemented using higher-order components or render properties in order to pass data, which can be confusing for people like me who are new to the front end. For example, when the business is complex, we have multiple high-level components related to the Context, one on top of the other, and the nesting reminds me of my fear when I wrote the Flutter.

Like this:

    <AuthenticationContext.Consumer>    
       {user => (        
          <LanguageContext.Consumer>         
               {language => (                
                    <StatusContext.Consumer>{status => ( ... ) }</StatusContext.Consumer>           
               )}       
         </LanguageContext.Consumer>   
       )}
    </AuthenticationContext.Consumer>
Copy the code

So of the several ways to write components that React offers, the function component is my favorite. The code is cleaner, there are no fancy new concepts, and it allows me to avoid dealing with this. Of course, so its capabilities are very limited. Functional components have no state, and most of the business logic has to deal with the life cycle. I still need to write components through classes to manage the life cycle and state, even if it’s a small component.

Then one day I found Hook and opened a new door! React has several hooks built in, *100%* backward compatibility, and direct support for all the familiar concepts in React: props, state, context, refs, and lifecycle. Furthermore, hooks provide a better way to combine these concepts, encapsulate your logic, and avoid nesting hell or similar problems. We can use state in function components, or we can perform some network requests after rendering.

Hook is actually a common function, which is a supplement to some capabilities in the function component, so we can use it directly in the function component, in the class component, we do not need it.

There are not many hooks provided by React. The most commonly used hooks are useState, useEffect and useContext. The others are applicable to more general or more boundary scenarios. UseState lets you manage state in a function component.

import { useState } from 'react'
const [ state, setState ] = useState(initialState)
Copy the code

Then we can access the state directly with state, set the state with setState, and the component will be automatically rerendered.

UseEffect is similar to adding code to componentDidMount and componentDidUpdate. We usually set the network request or Timer in these two methods. Now we can write to one place and return a cleanup function. It will be called at times like componentWillUnmount to perform some cleanup. UseEffect can replace all three methods.

import { useEffect } from 'react'
useEffect(didUpdate)
Copy the code

UseContext takes a Context object and returns the value of a Context.

import { useContext } from 'react'
const value = useContext(MyContext)
Copy the code

Can be used to replace Context Consumer. We’ll talk about how to use it later, but the previous nested hell can be resolved using useContext:

const user = useContext(AuthenticationContext)    
const language = useContext(LanguageContext)    
const status = useContext(StatusContext)
Copy the code

You should be interested in Hook. Instead of writing about providers and consumers and getting familiar with a lot of fancy concepts, people prefer this direct approach. I will show you how to implement a component in the way of class and Hook, to further show the convenience brought by Hook.

  • Kind of the way

To implement a component using a class, we need to define the state in the constructor, and we need to modify this to handle events, as follows:

import React from 'react'
class MyName extends React.Component {
	constructor(props) {
		super(props)
		this.state = { name: ' ' }
		this.handleChange = this.handleChange.bind(this)}handleChange(evt) {
		this.setState({ name: evt.target.value })
	}

	render() {
		const { name } = this.state
		return (
			<div>
				<h1>My name is: {name}</h1>
				<input type="text" value={name} onChange={this.handleChange} />
			</div>)}}export default MyName
Copy the code
  • Let’s now look at the way the function components work:
import React, { useState } from 'react'
function MyName() {
	const [name, setName] = useState(' ')

	function handleChange(evt) {
		setName(evt.target.value)
	}

	return (
		<div>
			<h1>My name is: {name}</h1>
			<input type="text" value={name} onChange={handleChange} />
		</div>)}export default MyName
Copy the code

There’s a lot less code, we’re using useState, there’s a lot less template code, we don’t have to deal with constructors and change this, we just call setName if we want to change the state. The whole code looks cleaner and easier to understand. We don’t care about maintaining the saved state, we just use the state using the useState function. Moreover, the function form makes it easier for the compiler to analyze and optimize the code, removing useless code blocks, and producing smaller files.

### Is it sweet? We can find that Hook prefer to us what we want to React statement, it is similar to the way we interface description, we just say what we want, rather than tell the framework how to do it, the code is more concise, convenient others understand with the late maintenance, through the way of function we can also share logic between components.

So how do hooks do such amazing things? To understand the underlying principles, let’s implement our own useState function from scratch to understand the process. This implementation won’t be exactly the same as the React implementation, but I’ll try to keep it simple and show the core principles.

First we define our own useState function, with the method signature you already know, passing an initial parameter.

function useState (initialState) {
Copy the code

Then we define a value to hold our state, and initially, its value will be the initialState that we pass to the function.

let value = initialState
Copy the code

Then we’ll define a setState function that rerenders the component when we change the state value.

function setState (nextValue) {        
    value = nextValue        
    ReactDOM.render(<MyName />, document.getElementById('root'))   
 }
Copy the code

The ReactDOM here is for rerendering. Finally, we will return the status value and setting method as an array:

    return [ value, setState ]
}
Copy the code

A simple Hook is implemented, which is a simple JS function that performs side effects such as setting a stateful value. Our Hook uses a closure to hold the state value. Since setState is under the same closure as value, our setState can access it. Similarly, we can’t access it directly outside of the closure without passing it in.

If we run our code now, we will find that the component’s state resets when it is rerendered, and then we cannot enter any text. That’s because every time we rerender it, we call useState, and that causes the value to be initialized so we have to figure out a way to save the state somewhere else so that it’s not affected by the rerender.

Let’s first try to use a global variable outside of the function to hold our state, so that our state will not be initialized by re-rendering.

let value
function useState (initialState) {
Copy the code

After we define a global variable in useState, our initialization code also needs to change:

 if (typeof value === 'undefined') value = initialState
Copy the code

That should be fine. But then we noticed that when we tried to call useState several times to manage multiple states, it always wrote values to the same global variable, and all useState methods operated on the same value! This is certainly not what we want.

So in order to support multiple useState calls, let’s try to improve this by replacing the variable with an array, okay?

let values = []
let currentHook = 0
Copy the code

Then change where the initial value is assigned:

 if (typeof values[currentHook] === 'undefined') 
      values[currentHook] = initialState
Copy the code

The most important thing is that our setState method is modified so that we only update the updated state value. We need to save the currentHook corresponding to the currentHook, because currentHook will always change.

    let hookIndex = currentHook    
    function setState (nextValue) {        
        values[hookIndex] = nextValue        
        ReactDOM.render(<MyName />.document.getElementById('root'))}Copy the code

Finally return:

return [ values[currentHook++], setState ]
Copy the code

Then we need to initialize currentHook when we start rendering:

function Name () {    
        currentHook = 0
Copy the code

Now ourHookIt’s working normally, so to speak

The React internal implementation is similar, but it is more complex and optimized. It handles the counters and global variables itself, and it does not require us to manually reset the counters. But the general principle is clear.

It’s not really a complicated scene, but imagine a situation where we need to display the first and last names, and we want to keep them separate, and we want to make the last name optional? We can first record whether last names are required with a status:

const [ enableFirstName, setEnableFirstName ] = useState(false)
Copy the code

Then we define a handler function:

function handleEnableChange (evt) { setEnableFirstName(! enableFirstName) }Copy the code

If the checkbox isn’t checked we’re not going to render the last name,

<h1>My name is: {enableFirstName ? name : ' '} {lastName}</h1>
Copy the code

Can we put the Hook definition in an if condition or a triplet operator? Like this:

const [ name, setName ] = enableFirstName        
    ? useState(' ') :' '.() = >{}]Copy the code

Now yarn Start runs our code and we can see that if the checkbox is not checked, the first name can still be changed, but the last name can be changed as much as you want. This is what we want.

When we check the box again, we can change the last name. But then something strange happened. The first name went to the last name.

This is because the order of Hook is very important. We all remember that when we implemented useState, we used currentHook to determine the state of the current call. Now we insert a Hook call out of air, which causes the order to be messed up. However, our global array does not change, resulting in the last name instead of the first name state.

Status before the check box is selected:

  • [false, ‘guest’]
  • The values are enableFirstName and lastName

After checking:

  • [true, ‘guest ‘,’ ‘]
  • The sequence is enableFirstName, name, and lastName

So the order in which hooks are called is very important! This restriction also exists in the React official Hook, and React has decided to stick with the current design. We should avoid this writing method. In case of this choice, we should declare the possible hooks directly whether they are used or not, or split them into independent components and use hooks in the components to change the question into whether to render a component or not. This is also recommended by the React team.

Sometimes it’s better to use hooks in conditional statements or loops, but why did the React team design them this way? Is there a better solution?

Someone proposed NamedHook:

// Note: This is not the actual React Hook API
const [ name, setName ] = useState('nameHook'.' ')
Copy the code

By doing this, we can avoid the data clutter we saw above. We set a unique name for each Hook call, but it takes time to come up with unique names, resolve name conflicts, and what do we do when a condition becomes false? What do we do if an element is removed from the loop? Should we clean up? What about memory leaks if you don’t clean up the state?

As we can see, this didn’t make things easier and introduced a lot of complex issues, so the React team ended up sticking with the current design, keeping the API as simple as possible, and we, in turn, were careful to use it in order.

Some of you might be tempted to ask, since hooks greatly simplify the structure of code and make it more maintainable, should we rewrite all the components using hooks? Of course not — hooks are optional. You can try hooks in some of your components. The React team has no plans to remove class components yet. There is no rush to reconstitute everything based on hooks. And Hook is not a silver bullet, we can feel with the Hook with a Hook to achieve the most appropriate place, for example, there are many components you deal with the similar logic, you can put the logic abstraction into a Hook, a team or a Hook is used to implement will be simple, state management is more complex in some places that still use class components would be better. So for the most part, we’ll still be mixing function components with class components.

# # #

Finally, I believe you to Hook with implementation principle must have a general understanding of the hooks is some simple js function, we take a look at the document they know how to use, now we know the Hook with the advantages of the restrictions, can be better to make a choice in the day-to-day development, this article’s code here: the sample code.