Explain in detail how to build your own react hooks

Time:2021-10-18
catalogue
  • 1. A commonly used hooks
    • 1.1 usestate: status hook
    • 1.2 useeffect: side effect hook
    • 1.3 usememo and usecallback
  • 2. Implement several custom hooks
    • 2.1 get the width and height of the window change
    • 2.2 timer useinterval
  • 3. Summary

    1. A commonly used hooks

    Several built-in hooks are provided in the official. Let’s briefly understand their usage.

    1.1 usestate: status hook

    If we need to update the data of page state, we can put it in the hook of usestate. For example, click the button once to add 1 to the data:

    
    const [count, setCount] = useState(0);
    
    return (<>
        <p>{ count}</p>
        <button onClick = {
            () => setCount(count + 1)
        }> add 1 </button>
        </>
    );

    In the typescript system, the type of count is the type of the current initial value by default. For example, the variable in the above example isnumberType. If we want to customize the type of this variable, we can define it after usestate:

    const [count, setCount] = useState<number | null>(null); //  The variable count is of type number or null

    At the same time, when using usestate to change the state, the whole state is replaced. Therefore, if the state variable is an object type data, I just want to modify one of the fields. When setstate is called in the previous class component, its internal data will be automatically merged.

    class Home extends React.Component {
        state = {
            name: 'wenzi',
            age: 20,
            score: 89
        };
    
        update() {
            this.setState({
                score: 98
            }); //  Internal auto merge
        }
    }

    However, when using usestate in the function component, you need to merge the data first and then call the method, otherwise the field will be lost.

    const [person, setPerson] = useState({
        name: 'wenzi',
        age: 20,
        score: 89
    });
    
    setPerson({
        ...person,
        {
            score: 98
        }
    }); //  Merge data first {Name: 'Wenzi', age: 20, score: 98}
    setPerson({
        score: 98
    }); //  Only the fields to be modified are passed in, and the name and age fields are lost

    1.2 useeffect: side effect hook

    Useeffect can be regarded as a combination of componentdidmount, componentdidupdate and componentwillunmount.

    The useeffect hook must be executed once after the component is initialized. Whether to update the component during re rendering depends on the second parameter passed in.

    • When the callback function is the only parameter, the callback will be executed every time the component is updated;
    • When there are two parameters, the callback is executed only when the data in the second parameter changes;
    • You only want to execute it once when the component is initialized. The second parameter can be passed in an empty array;

    We can take a look at this example. No matter whether you click the Add button or the set time button, the callback of useeffect will execute:

    
    const Home = () => {
        const [count, setCount] = useState(0);
        const [nowtime, setNowtime] = useState(0);
    
        useEffect(() => {
            console.log('count', count);
            console.log('nowtime', nowtime);
        });
    
        return ( <>
            <p>count: {count} </p>
            <p>nowtime: {nowtime} </p>
            <button onClick = {() => setCount(count + 1)}> add 1 </button>
            <button onClick = {() => setNowtime(Date.now())} > set now time </button>
        </>);
    };

    If it is changed to the following, the callback will be output on the console only when the count changes, and will not be output only when the nowtime value is modified:

    
    useEffect(() => {
        console.log('count', count);
        console.log('nowtime', nowtime);
    }, [count]);

    The callback function of useEffect can also return a function that is called before the end of the effect life cycle. To prevent memory leaks, the purge function is executed before the component is unloaded. In addition, if the component is rendered multiple times, the previous effect is cleared before the next effect is executed.

    Based on the above code, we modify it slightly:

    
    useEffect(() => {
        console.log('count', count);
        console.log('nowtime', nowtime);
    
        return () => console.log('effect callback will be cleared');
    }, [count]);

    Based on this mechanism, it is particularly suitable for some cases of adding binding and unbinding, such as monitoring the window size change of the page, setting the timer, establishing and disconnecting from the back-end websocket interface, etc. it can be expected that the useeffect can be encapsulated twice to form a user-defined hook. About custom hook, we will talk about it below.

    1.3 usememo and usecallback

    The variables and methods defined in the function component will be recalculated when the component is re rendered. For example, the following example:

    const Home = () => {
        const [count, setCount] = useState(0);
        const [nowtime, setNowtime] = useState(0);
    
        const getSum = () => {
            const sum = ((1 + count) * count) / 2;
            return sum + ' , ' + Math.random(); //  This random is to see the difference
        };
    
        return ( <>
            <p> count: {count}< /p>
            <p> sum: {getSum()}</p>
            <p> nowtime: {nowtime}</p>
            <button onClick = {() => setCount(count + 1)} > add 1 </button>
            <button onClick = {() => setNowtime(Date.now())}> set now time </button>
        </>);
    };

    There are two buttons, one is count + 1, and the other is to set the current timestamp. The getsun () method calculates the sum from 1 to count. Each time we click the Add button, the sum method recalculates the sum. However, when we click the set time button, the getsum method will also recalculate, which is unnecessary.

    Here we can use usememo to modify the following:

    
    const sum = useMemo(() => ((1 + count) * count) / 2 + ' , ' + Math.random(), [count]);
    
    <p> {sum} </p>;

    After modification, you can see that the sum value is recalculated only when the count changes. When you click the set time button, the sum is not recalculated. This is due to the features of the usememo hook:

    
    const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

    Usememo returns the value of return in the callback, and memoizedvalue is recalculated only when a dependency changes. This optimization helps to avoid expensive calculations at each rendering. If no dependency array is provided, usememo calculates a new value each time it renders.

    In the above example, the sum is recalculated only when the count variable changes, otherwise the sum value remains unchanged.

    Usecallback and usememo types, but usecallback returns a function, for example:

    
    const fn = useCallback(() => {
        return ((1 + count) * count) / 2 + ' , ' + nowtime;
    }, [count]);

    2. Implement several custom hooks

    In the official documents, the online and offline functions of friends are realized. Here we also learn to implement several hooks.

    2.1 get the width and height of the window change

    We can obtain the width and height of the window in real time by listening to the resize event. After encapsulating this method, we can automatically unbind the resize event before the end of the life cycle:

    
    const useWinResize = () => {
        const [size, setSize] = useState({
            width: document.documentElement.clientWidth,
            height: document.documentElement.clientHeight
        });
        const resize = useCallback(() => {
            setSize({
            width: document.documentElement.clientWidth,
            height: document.documentElement.clientHeight
        })
        }, [])
        useEffect(() => {
            window.addEventListener('resize', resize);
            return () => window.removeEventListener('resize', resize);
        }, []);
        return size;
    }

    It is also very convenient to use:

    
    const Home = () => {
        const {width, height} = useWinResize();
    
        return <div>
            <p>width: {width}</p>
            <p>height: {height}</p>
        </div>;
    };

    2.2 timer useinterval

    When using the timer in the front end, it is usually necessary to clear the timer before the end of the component life cycle. If the cycle of the timer changes, it is necessary to clear the timer first and then start it again according to the new cycle. The most commonly used scenario is the Jiugongge lucky draw. After the user clicks to start the lucky draw, it starts slowly and then gradually becomes faster. After the interface returns the winning result, it starts to slow down and finally stops.

    We can easily think of using useeffect to implement such a hook:

    
    const useInterval = (callback, delay) => {
        useEffect(() => {
            if (delay !== null) {
                let id = setInterval(callback, delay);
                return () => clearInterval(id);
            }
        }, [delay]);
    };

    Let’s try this code in the project:

    
    const Home = () => {
        const [count, setCount] = useState(0);
    
        useInterval(() => {
            console.log(count);
            setCount(count + 1);
        }, 500);
    
        return <div > {
            count
        } < /div>;
    };

    However, it’s strange after this section runs. The page changes from 0 to 1. The output of console.log (count) shows that the code is not stuck. What’s the problem?

    Props and state in the react component can be changed. React will re render them and “discard” any results about the last rendering, and there is no correlation between them.

    Useeffect () hook also “discards” the last rendering result. It will clear the last effect and create the next effect. The next effect locks the new props and state. This is also the reason why we try the simple example to work correctly for the first time.

    But setinterval will not be “discarded”. It will always reference the old props and state until you replace it – you can’t do it without resetting the time.

    The hook useref is used here. We store the callback in ref and update it when the callback is updatedref.currentValue of:

    const useInterval = (callback, delay) => {
        const saveCallback = useRef();
    
        useEffect(() => {
            //After each rendering, save the new callback to our Ref
            saveCallback.current = callback;
        });
    
        useEffect(() => {
            function tick() {
                saveCallback.current();
            }
            if (delay !== null) {
                let id = setInterval(tick, delay);
                return () => clearInterval(id);
            }
        }, [delay]);
    };

    When we use the new useinterval, the discovery can be self incremented

    Here we use a variable to control the speed of increase:

    const [count, setCount] = useState(0);
    const [diff, setDiff] = useState(500);
    
    useInterval(() => {
        setCount(count + 1);
    }, diff);
    
    return ( <div>
        <p> count: {count} </p>
        <p> diff: {diff}ms </p> 
        <p>
            < button onclick = {() = > setdiff (diff - 50)} > speed up 50ms < / button > 
            < button onclick = {() = > setdiff (diff + 50)} > slow down for 50ms < / button >
        </p>
    </div>);

    Click the two buttons respectively to adjust the speed of count increase.

    3. Summary

    Using react hook can do many interesting things. Here we just give a few simple examples. Later, we will have a deeper understanding of the principle of hook.

    The above is the details of how to build your own react hooks. For more information on how to build your own react hooks, please pay attention to other related articles of developeppaer!