Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

A case study of Hooks in React

2025-02-25 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >

Share

Shulou(Shulou.com)06/03 Report--

This article is about the case study of Hooks in React. The editor thinks it is very practical, so share it with you as a reference and follow the editor to have a look.

The simplest Hooks

First let's take a look at a simple stateful component:

Class Example extends React.Component {constructor (props) {super (props); this.state = {count: 0};} render () {return (

You clicked {this.state.count} times

This.setState ({count: this.state.count + 1}) > Click me);}}

Let's take a look at the version after using hooks:

Import {useState} from 'react';function Example () {const [count, setCount] = useState (0); return

You clicked {count} times

SetCount (count + 1)} > Click me);}

Isn't it much easier! As you can see, Example becomes a function, but this function has its own state (count), and it can also update its own state (setCount). The reason why this function is so great is that it injects a hook--useState, which makes our function a stateful function.

In addition to useState hook, there are many other hook, such as useEffect provides lifecycle hooks such as componentDidMount, useContext provides context functionality, and so on.

Hooks is essentially a special class of functions that can inject some special functionality into your functional components (function component). Huh? Does that sound a bit like the criticized Mixins? Is Mixins going to make a comeback in react? Of course not. We'll talk about the difference later. All in all, the goal of these hooks is to make you stop writing class and let function unify the world.

Why would React want a Hooks?

It's too troublesome to reuse a stateful component!

We all know that the core idea of react is to split a page into a bunch of independent, reusable components and concatenate them in the form of a top-down one-way data stream. But if you use react in large work projects, you will find that many react components in your project are actually lengthy and difficult to reuse. Especially those components written as class, they themselves contain state, so it becomes troublesome to reuse such components.

Before that, how can the official recommendation solve this problem? The answer is: render attributes (Render Props) and higher-order components (Higher-Order Components). We can run down a little bit and take a brief look at these two modes.

Rendering attributes refer to the use of a prop as a function to pass a nodes or component that needs to be dynamically rendered. As you can see in the following code, our DataProvider component contains all the state-related code, while the Cat component can be a simple presentation component, so that DataProvider can be reused separately.

Import Cat from 'components/cat'class DataProvider extends React.Component {constructor (props) {super (props); this.state = {target:' Zac'};} render () {return ({this.props.render (this.state)})}} ()} ()} / >

Although this pattern is called Render Props, it doesn't mean that you have to use a props called render. It is customary to write something like this:

... {data = > ()}

The concept of high-level components is better understood. To put it bluntly, a function takes a component as a parameter, and after a series of processing, it returns a new component. Looking at the code example below, the withUser function is a high-level component that returns a new component with the function it provides to retrieve user information.

Const withUser = WrappedComponent = > {const user = sessionStorage.getItem ("user"); return props = >;}; const UserPage = props = > (

My name is {props.user}!

); export default withUser (UserPage)

Both of these modes look good, and many libraries also use this pattern, such as our commonly used React Router. But if we look closely at these two patterns, we will find that they increase the hierarchical relationship of our code. For the most intuitive example, open devtool to see if your component level nesting is exaggerated. At this point, let's go back and see if the hooks example we gave in the previous section is much more concise and there are no extra levels of nesting. Write all the desired functions into a reusable custom hook, and when your component wants to use any function, just call this hook in the component.

The logic in the lifecycle hook function is too messy!

We usually want a function to do only one thing, but our lifecycle hook functions usually do a lot of things at the same time. For example, we need to initiate an ajax request in componentDidMount to get data, bind some event listeners, and so on. At the same time, sometimes we need to do the same thing at componentDidUpdate. When the project becomes complex, the code in this part becomes less intuitive.

Classes is really confusing!

Another troublesome thing when we use class to create react components is the pointing problem of this. To make sure that this is pointing correctly, we often write code like this: this.handleClick = this.handleClick.bind (this), or code like this: this.handleClick (e)} >. Once we accidentally forget to bind this, all kinds of bug will follow, which is troublesome.

There is another thing that bothers me. As I said in the previous react series, write your components in the form of stateless components as much as possible because they are easier to reuse and can be tested independently. However, most of the time, we write a simple and perfect stateless component in function. Later, because the component has to have its own state, we have to change the function to class.

In this context, Hooks was born out of nowhere!

What is State Hooks?

Going back to the example we used at the beginning, let's break down to see exactly what state hooks did:

Import {useState} from 'react';function Example () {const [count, setCount] = useState (0); return

You clicked {count} times

SetCount (count + 1)} > Click me);}

Declare a state variable

Import {useState} from 'react';function Example () {const [count, setCount] = useState (0)

UseState is a hook function that comes with react, and its function is to declare state variables. The useState function takes an argument to our state initial value (initial state), which returns an array whose item [0] is the current state value, and item [1] is a method function that can change the state value.

So what we did was actually declare a state variable count, set its initial value to 0, and provide a function setCount that can change the count.

The above expression, borrowed from es6's array deconstruction (array destructuring), makes our code look more concise. If you are not clear about this usage, you can read my article for 30 minutes to master the core content of ES6/ES2015 (above).

If you don't use array deconstruction, you can write it like this. In fact, array deconstruction is a very expensive thing, using the following writing, or using object deconstruction, the performance will be greatly improved. Specifically, you can go to the analysis of this article Array destructuring for multi-value returns (in light of React hooks), which will not be expanded in detail here, we will just use array deconstruction as recommended by the government.

Let _ useState = useState (0); let count = _ useState [0]; let setCount = _ useState [1]

Read status valu

You clicked {count} times

Isn't it super simple? Because our state count is just a simple variable, we don't need to write it as {this.state.count} anymore.

Update status

SetCount (count + 1)} > Click me

When the user clicks the button, we call the setCount function, which receives the modified new state value. The rest is up to react, where react will re-render our Example component and use the updated new state, count=1. Here we need to stop and think, Example is essentially a normal function, why can it remember the previous state?

A crucial issue.

Here we find the problem. Generally speaking, the variable we declare in a function will be destroyed when the function is run (let's not consider closures and so on here). For example, consider the following example:

Function add (n) {const result = 0; return result + 1;} add (1); / / 1add (1); / / 1

No matter how many times we call the add function, the result is 1. Because every time we call add, the result variable starts with the initial value of 0. Then why is it that every time the above Example function is executed, it takes the state value of the last execution as the initial value? The answer is: react helps us remember it. As for the mechanism by which react is remembered, we can think again.

What if a component has more than one status value?

First of all, useState can be called multiple times, so we can write this:

Function ExampleWithManyStates () {const [age, setAge] = useState (42); const [fruit, setFruit] = useState ('banana'); const [todos, setTodos] = useState ([{text:' Learn Hooks'}])

Second, the initial value received by useState does not have to be a simple data type such as string/number/boolean, and it can accept objects or arrays as parameters. The only thing to note is that what our this.setState did before was to merge the state and return to a new state, while useState returned to the new state after directly replacing the old state. Finally, react also provides us with a useReducer hook, if you prefer a redux-style state management solution.

We can see from the ExampleWithManyStates function that useState is independent of each other no matter how many times it is called. This is crucial. Why would you say that?

In fact, when we look at the "form" of hook, it is somewhat similar to Mixins, which has been rejected by the authorities before, which provides a kind of "plug-and-plug function injection" ability. Mixins is denied because the Mixins mechanism allows multiple Mixins to share the data space of an object, so it is difficult to ensure that the states of different Mixins dependencies do not conflict.

And now our hook, on the one hand, it is directly used in function, not class;, on the other hand, each hook is independent of each other, and different components calling the same hook can also ensure the independence of their respective states. This is the essential difference between the two.

How does react ensure that multiple useState are independent of each other?

Let's take a look at the ExampleWithManyStates example given above. We called useState three times, and each time we passed a parameter with only one value (for example, 42 useState). We didn't tell react which key these values correspond to, so how did react ensure that the three useState found its corresponding state?

The answer is that react is based on the order in which useState appears. Let's take a look at the details:

/ / first render useState (42); / / initialize age to 42 useState ('banana'); / / initialize fruit to banana useState ([{text:' Learn Hooks'}]); / / second render useState (42); / / read the value of the state variable age (parameter 42 is directly ignored) useState ('banana') / / read the value of the state variable fruit (when the parameter banana is directly ignored) useState ([{text: 'Learn Hooks'}]); / /.

If we change the code:

Let showFruit = true;function ExampleWithManyStates () {const [age, setAge] = useState (42); if (showFruit) {const [fruit, setFruit] = useState ('banana'); showFruit = false;} const [todos, setTodos] = useState ([{text:' Learn Hooks'}])

thus,

/ / first rendering useState (42); / / initializing age to 42 useState ('banana'); / / initializing fruit to banana useState ([{text:' Learn Hooks'}]); / / second rendering useState (42); / / reading the value of the state variable age (parameter 42 passed at this time is directly ignored) / / useState ('banana'); useState ([{text:' Learn Hooks'}]) / / the value of the state variable fruit was read, resulting in an error

In view of this, react stipulates that we must write hooks in the outermost layer of the function, not in conditional statements such as ifelse, to ensure that the execution order of hooks is consistent.

What is Effect Hooks?

We added a new feature to the example in the previous section:

Import {useState, useEffect} from 'react';function Example () {const [count, setCount] = useState (0); / / similar to componentDidMount and componentDidUpdate: useEffect (() = > {/ / update document title document.title = `You clicked ${count} times`;}); return (

You clicked {count} times

SetCount (count + 1)} > Click me);}

Let's compare and see, if there were no hooks, what would we write?

Class Example extends React.Component {constructor (props) {super (props); this.state = {count: 0};} componentDidMount () {document.title = `You clicked ${this.state.count} times`;} componentDidUpdate () {document.title = `You clicked ${this.state.count} times`;} render () {return ()

You clicked {this.state.count} times

This.setState ({count: this.state.count + 1}) > Click me);}}

The stateful components we write usually have a lot of side effect, such as initiating ajax requests to get data, adding some listening registrations and unregistrations, manually modifying dom, and so on. We have previously written these side-effects functions in lifecycle function hooks, such as componentDidMount,componentDidUpdate and componentWillUnmount. Today's useEffect is equivalent to a collection of hooks for declaring periodic functions. It equals one to three.

At the same time, as mentioned above, hooks can be used many times and independent of each other. So it makes sense to give each side effect a separate useEffect hook. As a result, these side effects are no longer piled up in lifecycle hooks, and the code becomes clearer.

What did useEffect do?

Let's go through the logic of the following code again:

Function Example () {const [count, setCount] = useState (0); useEffect (() = > {document.title = `You clicked ${count} times`;})

First, we declare a state variable count and set its initial value to 0. Then we told react that our component had a side effect. We passed an anonymous function to useEffecthook, which is our side effect. In this example, our side effect is to call browser API to change the document title. When react wants to render our components, it first remembers the side effects we use. After react updates the DOM, it executes the side-effect functions we defined in turn.

Here are a few points to pay attention to:

First, the function passed to useEffect is called for the first react rendering and each subsequent rendering. Before that, we used two declaration cycle functions to represent the first rendering (componentDidMount) and the re-rendering caused by subsequent updates (componentDidUpdate).

Second, the execution of the side effect functions defined in useEffect does not prevent the browser from updating the view, which means that these functions are executed asynchronously, while the code in the previous componentDidMount or componentDidUpdate is executed synchronously. This arrangement is reasonable for most side effects, except in some cases. For example, sometimes we need to calculate the size of an element based on DOM and then re-render. In this case, we want this re-rendering to happen synchronously, that is, it will happen before the browser actually draws the page.

How to unbind some side effects of useEffect

This is a common scenario. When we add a registration to componentDidMount, we have to clear the registration we added immediately in componentWillUnmount, that is, before the component is logged out, otherwise there will be a memory leak.

How do I get rid of it? Let's just return a new function from the side effect function we passed to useEffect. This new function will be executed after the next re-rendering of the component. This pattern is common in some pubsub pattern implementations. Look at the following example:

Import {useState, useEffect} from 'react';function FriendStatus (props) {const [isOnline, setIsOnline] = useState (null); function handleStatusChange (status) {setIsOnline (status.isOnline);} useEffect () = > {ChatAPI.subscribeToFriendStatus (props.friend.id, handleStatusChange); / / pay attention to this order: tell react to execute cleanup return function cleanup (props.friend.id, handleStatusChange) {ChatAPI.unsubscribeFromFriendStatus (props.friend.id, handleStatusChange) after the next re-rendering of the component and before the next ChatAPI.subscribeToFriendStatus call. };}); if (isOnline = = null) {return 'Loading...';} return isOnline? 'Online':' Offline';}

Here is a point to pay attention to! This unbinding mode is not the same as componentWillUnmount. ComponentWillUnmount will only be executed once before the component is destroyed, while the function in useEffect will be executed again every time the component is rendered, including the cleanup function returned by the side function. So let's take a look at the following question.

Why should the side effect function be executed every time the component is updated?

Let's take a look at the previous pattern:

ComponentDidMount () {ChatAPI.subscribeToFriendStatus (this.props.friend.id, this.handleStatusChange);} componentWillUnmount () {ChatAPI.unsubscribeFromFriendStatus (this.props.friend.id, this.handleStatusChange);}

Very clear, we register in componentDidMount, and then clear the registration in componentWillUnmount. But what if props.friend.id changes at this time? We have to add another componentDidUpdate to handle this situation:

... ComponentDidUpdate (prevProps) {/ / unbind the previous friend.id from ChatAPI.unsubscribeFromFriendStatus (prevProps.friend.id, this.handleStatusChange); / / re-register the new friend.id ChatAPI.subscribeToFriendStatus (this.props.friend.id, this.handleStatusChange);}.

You see that? It's tedious, but we don't have this problem with useEffect, because it executes again every time the component is updated. So the order in which the code is executed is as follows:

1. Page rendering for the first time

two。 Register for friend.id=1 's friend

3. Suddenly friend.id becomes 2.

4. Page re-rendering

5. Clear the binding of friend.id=1

6. Register for friend.id=2 's friend

...

How to skip some unnecessary side effect functions

According to the idea in the previous section, it is obviously uneconomical to execute these side-effect functions every time you re-render. How to skip some unnecessary calculations? We just need to pass the second parameter to useEffect. Use the second argument to tell react that the side effect function we passed (the first parameter) will be executed only if the value of this parameter changes.

UseEffect (() = > {document.title = `document.title`;}, [count]); / / the sentence `document.title` will be re-executed only when the value of count changes.

When we pass an empty array [] in our second parameter, it is actually executed only on the first rendering. This is the mode of componentDidMount plus componentWillUnmount. However, this usage may lead to bug, so use less.

What other Effect Hooks do you have with you?

In addition to the useState and useEffect,react highlighted above, many useful hooks are also provided to us:

UseContext

UseReducer

UseCallback

UseMemo

UseRef

UseImperativeMethods

UseMutationEffect

UseLayoutEffect

I will not introduce them one by one, and you will consult the official documents on your own.

How to write a custom Effect Hooks?

Why write an Effect Hooks yourself? In this way, we can extract the logic that can be reused and turn it into "plugs" that can be plugged and unplugged at will. I will plug into which component I want to use, so easy! Look at a complete example and you will understand.

For example, we can extract the function of judging whether a friend is online in the FriendStatus component written above, and create a new hook of useFriendStatus to determine whether an id is online or not.

Import {useState, useEffect} from 'react';function useFriendStatus (friendID) {const [isOnline, setIsOnline] = useState (null); function handleStatusChange (status) {setIsOnline (status.isOnline);} useEffect () = > {ChatAPI.subscribeToFriendStatus (friendID, handleStatusChange); return () = > {ChatAPI.unsubscribeFromFriendStatus (friendID, handleStatusChange);};}); return isOnline;}

At this point the FriendStatus component can be abbreviated as:

Function FriendStatus (props) {const isOnline = useFriendStatus (props.friend.id); if (isOnline = null) {return 'Loading...';} return isOnline? 'Online':' Offline';}

It's Perfect! If we have another list of friends at this time, we also need to show whether it is online or not:

Function FriendListItem (props) {const isOnline = useFriendStatus (props.friend.id); return ({props.friend.name});} Thank you for reading! This is the end of this article on "case study of Hooks in React". I hope the above content can be of some help to you, so that you can learn more knowledge. if you think the article is good, you can share it for more people to see!

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Development

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report