Can't encapsulate hook? Take a look at how these six hook of ahooks are done

June 26, 2023 1258hotness 0likes 0comments

1 useUpdate

how do I force a component to refresh in a react function component? Although react does not provide a native method, we know that when the state value changes, the react function component will be refreshed, so useUpdate takes advantage of this. The source code is as follows:

import { useCallback, useState } from 'react';

const useUpdate = () => {
  const [, setState] = useState({});

  return useCallback(() => setState({}), []);
};

export default useUpdate;

you can see the return value function of useUpdate, which calls setState with a new object each time, triggering the update of the component.

2 useMount

although the react function component does not have the life cycle of mount, we still have this requirement, that is, the requirement of executing once after the component is rendered for the first time can be encapsulated by useEffect. If you only need to set the dependency to an empty array, then you can only perform a callback after the rendering is completed:

import { useEffect } from 'react';

const useMount = (fn: () => void) => {

  useEffect(() => {
    fn?.();
  }, []);
};

export default useMount;

3 useLatest

react function component is an interruptible, repeatable function, so every time there is a change in state or props, the function will be re-executed. We know that the scope of the function is fixed when the function is created. If the internal function is not updated, then the external variables obtained by these functions will not change. For example:

import React, { useState, useEffect } from 'react';
import { useLatest } from 'ahooks';


export default () => {
  const [count, setCount] = useState(0);

  useEffect(() => {
    const interval = setInterval(() => {
      setCount(count + 1);
    }, 1000);
    return () => clearInterval(interval);
  }, []);

  return (
    <>
      <p>count: {count}</p>
    </>
  );
};

this is an example of regularly updating the count value, but the above code only keeps count at 1, because the scope of the function in setInterval is set when it is created, and the count it gets is always 0. When the setCount is executed, it will trigger the re-execution of the function. When the count value becomes 1, the count is no longer a count variable in its scope. Each execution of the function creates a new environment, and hooks such as useState and useRef provide the ability to maintain the state after the function is re-executed, but for those functions that are not recreated, their scope stays at the time of creation forever. The simple and direct way to make count update correctly is as follows: update count variable directly while setCount, that is, directly change the value of this closure variable, which is also allowed in JS.

import React, { useState, useEffect } from 'react';
import { useLatest } from 'ahooks';


export default () => {
  let [count, setCount] = useState(0);

  useEffect(() => {
    const interval = setInterval(() => {
    count = count + 1
      setCount(count + 1);
    }, 1000);
    return () => clearInterval(interval);
  }, []);

  return (
    <>
      <p>count: {count}</p>
    </>
  );
};

setCount is to refresh the function and update the count value of the function, while directly assigning a value to count is to update the closure variables maintained in the scheduled task function. This is obviously not a good solution, and a better way is to let the scheduled task function get the latest count value of the function. The count returned by useState is a new variable every time, that is, the address of the variable is different. You should let the scheduled task function reference an object with the same variable address, and record the latest count value in this object. To implement this function, you need to use useRef, which can help us return the object with the same variable address every time the function is refreshed. The implementation is as follows:

import React, { useState, useEffect, useRef } from 'react'

export default () => {
  const [count, setCount] = useState(0)

  const latestCount = useRef(count)
  latestCount.current = count

  useEffect(() => {
    const interval = setInterval(() => {
      setCount(latestCount.current + 1)
    }, 1000)
    return () => clearInterval(interval)
  }, [])

  return (
    <>
      <p>count: {count}</p>
    </>
  )
}

you can see that the latestCount obtained by the timing function is always the variable when it is defined, but because of useRef, the address of the variable remains the same every time the function executes, and the latest value of count is assigned to latestCount.current, and the timing function can get the latest count value. So this function can be encapsulated as a useLatest to get the latest values.

import { useRef } from 'react';

function useLatest<T>(value: T) {
  const ref = useRef(value);
  ref.current = value;

  return ref;
}

export default useLatest;

the above example is to illustrate the role of useLatest, but for this example, it is only for count+1 and can also be obtained through the setCount method itself. Although the setCount page in the scheduled task function has always been the initial function, its function is to obtain the latest count value by passing the function. The code is as follows:

  const [count, setCount] = useState(0)
  useEffect(() => {
    const interval = setInterval(() => {
      setCount(count=>count+1)
    }, 1000)
    return () => clearInterval(interval)
  }, [])

4 useUnMount

with useMount, there will be useUnmount, which takes advantage of the fact that the function of useEffect returns a cleanup function that triggers when the component is unloaded and the dependency of useEffect changes. Under normal circumstances, what we do when we are supposed to be useEffect, the returned cleanup function clears the timer accordingly, for example, useEffect creates a timer, then the returned cleanup function should clear the timer:

 const [count, setCount] = useState(0);
 useEffect(() => {
    const interval = setInterval(() => {
    count = count + 1
      setCount(count + 1);
    }, 1000);
    return () => clearInterval(interval);
  }, []);

so useUnMount takes advantage of this cleanup function to implement useUnmount. The code is as follows:

import { useEffect } from 'react';
import useLatest from '../useLatest';

const useUnmount = (fn: () => void) => {
  const fnRef = useLatest(fn);

  useEffect(
    () => () => {
      fnRef.current();
    },
    [],
  );
};

export default useUnmount;

uses useLatest to hold the latest value of fn, writes an empty useEffect, relies on an empty array, and executes only when the function is unloaded.

5 useToggle and useBoolean

useToggle encapsulates the change of state between two values, while useBoolean takes advantage of useToggle. Fixed two values can only be true and false. Take a look at their source code:

function useToggle<D, R>(defaultValue: D = false as unknown as D, reverseValue?: R) {
  const [state, setState] = useState<D | R>(defaultValue);

  const actions = useMemo(() => {
    const reverseValueOrigin = (reverseValue === undefined ? !defaultValue : reverseValue) as D | R;

    const toggle = () => setState((s) => (s === defaultValue ? reverseValueOrigin : defaultValue));
    const set = (value: D | R) => setState(value);
    const setLeft = () => setState(defaultValue);
    const setRight = () => setState(reverseValueOrigin);

    return {
      toggle,
      set,
      setLeft,
      setRight,
    };
    // useToggle ignore value change
    // }, [defaultValue, reverseValue]);
  }, []);

  return [state, actions];
}

as you can see, you can set the initial value and the opposite value when calling useToggle. The default initial value is that false,actions is encapsulated in useMemo to improve performance, so it is not necessary to recreate these functions every time you render. SetLeft is the initial setting value, setRight is the opposite value, set is set by the user at will, and toggle is to switch between 2 values. UseBoolean is encapsulated on the basis of useToggle, which makes it more concise and convenient for us to use.

export default function useBoolean(defaultValue = false): [boolean, Actions] {
  const [state, { toggle, set }] = useToggle(defaultValue);

  const actions: Actions = useMemo(() => {
    const setTrue = () => set(true);
    const setFalse = () => set(false);
    return {
      toggle,
      set: (v) => set(!!v),
      setTrue,
      setFalse,
    };
  }, []);

  return [state, actions];
}

Summary

this article introduces six simple hook encapsulated in ahooks. Although simple, we can learn the idea and function of custom hook through their practice, that is, encapsulate some logic that can be reused. In the actual project, we can package the hook suitable for the project with this consciousness.

InterServer Web Hosting and VPS

Aaron

Hello, my name is Aaron and I am a freelance front-end developer

Comments