The introduction

This article will introduce some tips on using React hooks in your daily development. It will focus on the use of two hooks: useState and useEffect.

1. In the age of the React Class, why do we use React hooks?

First, let’s be clear about the following points:

  • React hooks are completely optional, you can rewrite old redundant components, you can use them in new components, or you can learn to do nothing, like a salt fish 🐟.
  • 100% backward-compatible Hooks do not include any destructive < subversive > updates.
  • – Class will not be removed, it will still be accompanied by hooks, but officially it is recommended to use hooks,Because in most cases hooks do what a class can do.

When using react Class components, it is difficult to reuse state logic across components because we have to implement business logic (such as setState) through various methods mounted on instance object this. Too much “This” can easily lead to confusion among junior developers (such as the question of who this refers to, which also happens in VUe2).

3, complex components become redundant, in the later stages of the project, and the class components are likely to become difficult to maintain, imagine thousands of lines of a component, there will be hundreds of internal function, because each function has its own scope, so if we want to use in these methods the state of props attribute, we have to redefine, The functionComponent feature of hooks prevents this from happening, reducing code duplication and improving efficiency. (It’s hard to say that there are some performance issues associated with this, but for most projects it doesn’t affect performance too much)

This is also one of the advantages of hooks, which are easy for those who know React, and easy for those who do not yet use React, because vue3 is the same in mind design as React hooks. Of course, React has been considered more difficult to learn than VUE for many years, so according to the recruitment ads, other companies use VUE more than famous companies. Hooks made it easier to some extent.

5, hooks to differentiate logic more granular, is the use of hooks can be encapsulated with dom components, not only can encapsulate some public response type logic method (truthfully when listening to return a user login, this state we can reuse in any hooks components), apparently class components in dealing with this kind of thing, It’s a little bit more complicated.

UseState and useEffect

Let’s briefly introduce how useState and useEffect are used

const [demo1Name, setDemo1Name] = useState(' ');
Copy the code

Return a two-entry array, the first of which is the latest value returned by useState, and the second, which can be thought of as a named setState, which we initialize to an empty string by passing useState as the only argument

  // Declare multiple state variables
  const [age, setAge] = useState(42);
  const [fruit, setFruit] = useState('banana');
  const [todos, setTodos] = useState([{ text: 'learning Hook' }]);
Copy the code

UseEffect Hook (componentDidMount); ComponentDidUpdate and componentWillUnmount.

// Every time componentDidUpdate is triggered
    useEffect(() = > {
        console.log('I keep triggering useEffect1')})ComponentDidMount is triggered only when the component is initialized
    useEffect(() = > {
        console.log('I triggered useEffect2 when I initialized.')}, [])// Whenever the nameZyy changes and is initialized, it triggers something similar to vue watch but stronger than watch
    useEffect(() = > {
        console.log('Because nameZyy changed, I triggered useEffect3')
    }, [nameZyy])

    // Whenever nameXyb changes and is initialized
    useEffect(() = > {
        console.log('Because nameXyb changed, I triggered useEffect4')
    }, [nameXyb])
    
    // The function componentWillUnmount in return is triggered when the component is destroyed
    useEffect(() = >{
        return () = > {
            // Clear timer, clear event listener function}}, [])Copy the code

Each component update is equivalent to re-executing the componentfunction

Sharing redirects vscode

  • In a class component, an update view operation is usually triggered by the render function and a specific Update lifecycle, and its internal state points to a unique memory address via this instance, thus ensuring consistency of component state after an update.

  • In this case, hooks use methods in React to change this component from stateless to useState. In this case, hooks use methods in React to change this component from stateless to useState. In this case, hooks use methods in React to change this component from stateless to useState. Inside useState is a variable that you defined when you initialized it.

In order to better demonstrate the above theory, I used the concept provided by the netease Cloud team, which is similar to the concept of animation frames (we call each trigger render a frame). It is easier to understand the implementation mechanism of hooks

The variable methods defined in each frame are independent of each other

To explain the concept of frames, here is an example of a timer

Sharing redirects vscode

function Demo2() {
    const [num, setNum] = useState(0);
    // componentDidMount
    useEffect(() = > {
        setInterval(() = > {
            setNum(num + 1) // num is always 0, I always take the first frame which is the initialization value
            console.log(num)
        }, 1000);
    }, [])
    useEffect(() = >{
        console.log(num,'I'm going to be a 1, I'm going to stay a 1, I'm not going to trigger render.')
    },[num])
    return (
        <Card title="demo2">
            <p>{num}</p>
        </Card>)}Copy the code

How does demo2 perform above, first initialize the first frame to simulate the code for the first frame below

function Demo2_1() {
    const num_1 = 0;
    // componentDidMount
    useEffect(() = > {
        setInterval(() = > {
            setNum(num_1 + 1)
            console.log(num_1)
        }, 1000);
    }, [])
    useEffect(() = >{
        console.log(num_1,'WHEN I initialize, I execute the following 0. On frame 2, I change to 1. After that, IT stays 1.)
    },[num_1])
    return (
        <Card title="demo2">
            <p>{num_1}</p>
        </Card>)}Copy the code

The constant num_1 and two useEffect functions are defined inside the Demo2 component, one of which useEffect is executed only on the first frame of initialization. The results are: Set num_1 = 0, setNum = 0, useEffect console.log = num_1 = 1, useEffect console.log = num_1 = 1 It will no longer trigger the render.

Sharing redirects vscode

If you don’t find the above examples convincing, OK! Let me give you an example of netease BIG V

const Demo3 = (props) = > {
    const { count } = props;
    const handleClick = () = > {
        setTimeout(() = > {
            alert(count);
        }, 3000);
    };
    return (
        <Card title="demo3">
            <p>{count}</p>
            <button onClick={handleClick}>I play my point</button>
        </Card>
    );
}
export default Demo3;
Copy the code

We pass count to the child component via props, which starts at 0 and increments by 1 every second. When we click on the button, alert’s count is not the value that is still incrementing on the page, it is the value that is equal to the moment we click. Thus, it proves that the value of each frame is independent, just an ordinary data.

  • Of course this only happens in hooks; with the class component, since this refers to the same instance, the internal property value of this keeps changing.
class Demo4 extends Component {
    handleClick = () = > {
        setTimeout(() = > {
            alert(this.props.count);
        }, 3000);
    };
    render() {
        const {
            count
        } = this.props;
        return (
            <Card title="demo4">
                <p>{count}</p>
                <Button onClick={this.handleClick}>Click me play me 4</Button>
            </Card>); }}Copy the code

summary

In the two examples above, the hooks function redefines internal variables every time Render is triggered, using the concept of animation frames. React, for example, helps us maintain a set of states that are stored independently of the function.

To be clear, the variables we deconstructed from useState as constants in the function are plain data, and there are no data binding operations to force DOM updates. After calling setState in hooks, React will re-execute the Render function and that’s it.

Never cheat useEffect

For useEffect, the time to execute is after all DOM changes have been made and the browser has rendered the page

const Demo5 = (props) = > {
    const {
        id
    } = props;

    useEffect(() = > {
            setTimeout(() = > {
                console.log(id, 'I'm the ID of DEMO5')},100)}, [])return (
        <Card title="demo5">I am a demo5 {id}</Card>)}Copy the code

Usually we will have this demand, id is a dynamic returned by the interface, demo5 as a subcomponent he also want to use the id request an other interface, at this time, actually we rely on the dynamic id, but wrote in the subcomponents are unable to get the latest id value, because the child components in the id value, when there is a change will have finished rendering.

We have two solutions to this problem: 1. UseEffect specifies the id dependency in the second parameter array and shorts the protection.

    useEffect(() = > {
        if (id) {
            setTimeout(() = > {
                console.log(id, 'I'm the ID of DEMO5')},100)
        }
    }, [id])
Copy the code

2. Render child components when the ID value changes. But in cases such as wikis where article components are not destroyed, the advantage of using hooks comes into play.

The practice of useMemo

|  ![](https://assets.che300.com/wiki/2021-03-29/16170023709952396.png)
|_
Copy the code
import React, { useState } from 'react'
import { Button,Card } from 'antd';

export default() = > {const [count, setCount] = useState(1);
    const [val, setValue] = useState('Zhu Yuyi');
    function add() {
        console.log('I did it again.');
        let sum = 0;
        for (let i = 0; i < count * 100; i++) {
            sum += i;
        }
        return sum;
    }
    
    return (
        <Card title="demo6">
            <h4>{count}---{add()}</h4>
            {val}
            <div>
                <Button onClick={()= > setCount(count + 1)}>+1</Button>
                <input value={val} onChange={event= > setValue(event.target.value)} />
            </div>
        </Card>
    );
}
Copy the code

Running demo6 above, we can see that the add function is executed whenever we click on Button or add a value to input. However, the add function has nothing to do with val, resulting in a performance penalty. So in this case we need to use the useMemo hook.

export default() = > {const [count, setCount] = useState(1);
    const [val, setValue] = useState('Zhu Yuyi');
    const add = useMemo(() = > {
        console.log('I only execute if count changes');
        let sum = 0;
        for (let i = 0; i < count * 10; i++) {
            sum += i;
        }
        return sum;
    }, [count])

    return (
        <Card>
            <h4>{count}-{add}</h4>
            {val}
            <div>
                <Button onClick={()= > setCount(count + 1)}>+c1</Button>
                <input value={val} onChange={e= > setValue(e.target.value)} />
            </div>
        </Card>
    );
}
Copy the code

The add function is triggered only when we click the Button, saving performance. Here is a practice diagram from the question bank project

List filters are cached to reduce unnecessary rendering and improve performance. (The original class component didn’t take this into account.)

In the old class component, we used shouldComponentUpdate to determine if the current property and state are the same as the last one to avoid unnecessary updates to the component. The comparison is for all the attributes and states of this component. There is no way to update some elements and not others based on the return value of shouldComponentUpdate. UseMemo in the function component can actually do this job.

Powerful custom hooks

import { useEffect, useState, } from 'react'

export function useOnline() {

    const [online, setOnline] = useState(false);

    useEffect(() = > {
        let timer = setInterval(() = > {
            setOnline(c= > {
                return! c }) },3000)
        return () = > {
            clearInterval(timer)
        }
    }, [])

    return online
}
Copy the code

The hooks above are special; they define an online state that changes true false every three seconds to simulate whether the user is online or offline. We can just go back to that state. How do you use it?

export default() = > {const online = useOnline();
    console.log(online)

    return (
        <Card title="demo8">
            <div>{online ? 'I'm online' : 'I'm offline '}</div>
        </Card>
    );
}
Copy the code

In this example, call const online = useOnline() directly; To obtain the response data online. This is where custom hooks are powerful, and smart developers have developed all sorts of hooks that work because of them

How do parent components call child component methods

Use the useRef useImperativeHandle forwardRef in conjunction with the class component

interface useRefTypes {
    getTopicList: () = > void
}

const topicRef = useRef<useRefTypes>(null); .const topicDom = (
     <div className={styles.describeContainer}>
          <Topic
               dispatch={dispatch}
               subjectId={id}
               loading={loading}
               userId={_id}
               ref={topicRef}
          />
     </div>)...// Submit and post comments
const submitAndTopic = () = > {
    if(! submitValue) { notification.warning({message: 'tip'.description: 'Please enter your answer'
        })
        return
    }
    confirm({
        title: 'tip'.content: 'Do you want to synchronize your answers to the comments section? '.okText: 'sure'.cancelText: 'cancel'.onOk: () = > {
            submit();
            dispatch({
                type: 'subjectDetails/addTopic'.payload: {
                    subjectId: id,
                    content: submitValue,
                    parentTopicId: 'topTopic'
                },
                callback: () = > {
                    if (topicRef && topicRef.current) {
                        if(topicRef.current.getTopicList) { topicRef.current.getTopicList(); }}}})}})}Copy the code

Use usRef in parent (top code) and useImperativeHandle forwardRef in child (bottom code)

Wrap the method in the forwardRef
const Topic: React.FC<propsType> = forwardRef((props, ref) = >{... useImperativeHandle(ref,() = > ({
        getTopicList
    }));
}
Copy the code

Wrap the method in the forwardRef and internally call useImperativeHandle (useImperativeHandle allows you to customize the instance value exposed to the parent component when using the ref. In most cases, imperative code like ref should be avoided. UseImperativeHandle should be used with the forwardRef.

The problem

The questions my studious colleagues discussed with me before the meeting are as follows

Are Hooks slow to create functions at render time?

Don’t. In modern browsers, the raw performance of closures and classes differs significantly only in extreme scenarios. In addition, it can be argued that the Hooks design is more efficient in some ways:

  • HooksTo avoid theclassAdditional overhead is required, such as the cost of creating class instances and binding event handlers in constructors.
  • The language-conforming code is in useHooksDoes not require deep component tree nesting. This is common in code bases that use higher-order components, render props, and context. The React component tree is smallThe workloadIt also decreases.

We place the code that will only be executed in useEffect inside the useEffect callback so that the useEffect internal function declaration is executed only when the dependent value changes.

2,Use only at the top levelHook?

Yes yes!! Never call hooks in loops, conditions, or nested functions. Make sure you always call them at the top of your React function. By following this rule, you can ensure that hooks are called in the same order every time you render. This allows React to keep the hook state correct between multiple useState and useEffect calls.

3,useStateThe synchronization feature is displayed in the callback function of the interface, i.e., each timesetStateTriggered multiple timesrender

To fix this, use the unstable_batchedUpdates method provided by React, so that two unrelated setstates do not trigger two render sessions, but one.

import { unstable_batchedUpdates as batchedUpdates } from 'react-dom';

dispatch({
    type:'xxxx'.callback:() = >{
        batchedUpdates(() = >{
            // Two different states
            setIsMeThumbsUpState(flag= >! flag); setThumbsUpCount(1); })}})Copy the code

Reference documentation

React official Chinese website juejin.cn/post/684490… Blog.csdn.net/qq_44753552… UseEffect and useLayoutEffect addeventlistener: useEffect and useLayoutEffect

other

Lay particular emphasis onuseEffectAlways pay attention to the state value in the event listener callback when writing the event listener in. ifuseEffectIf no dependency is addedstateAlways the initial value