React

Custom hook 기초

moonkey 2022. 12. 8. 21:02

시작하며

React를 통해 개발하면서 컴포넌트마다 똑같은 코드가 반복되곤 하는데 그런 코드가 심지어 길이도 작지 않다면 컴포넌트가 길어져서 곤란하다. 이런 문제를 custom hook을 통해 해결할 수 있는 부분이 있을 것이라 생각이 들어 새로 공부하게 되었다. 

 

Custom hook의 장점

  • 클래스 컴포넌트보다 적은 코드로 동일한 기능을 구현할 수 있다. 
  • 코드 길이를 줄이면서 가독성은 높일 수 있다.

Custom hook 규칙

  1. 최상위에서 hook을 호출한다 (반복문이나 조건문에서 hook을 호출하지 않는다.)
  2. React 함수 내에서만 hook을 호출한다.
  3. hook의 이름을 'use'로 시작한다.

Hook 만들 때 고려해야 하는 점

  • custom hook을 만들 때는 다른 hook의 state에 영향을 끼치게 해서는 안된다. 즉, 각각의 state가 고유성을 가지고 있어야 하는데  고유성을 지키지 않는 경우 예기치 못한 문제나 이슈가 발생했을 때 그 원인을 파악하기도 어렵다. 
  • custom hook이 에러나 코드를 보는데 있어서 인과관계를 잘 파악할 수 있게 해야 한다. 

 

기존에 쓰던 반복되는 로직을 가능하면 custom hook으로 깔끔하게 만들려고 했는데 그랬다가는 가독성이나 오히려 hook을 수정하는데 더 시간이 걸리고 에러도 발생하겠다는 생각이 들었다. 그러면 어떤 상황에서 hook을 사용하는게 좋을지 궁금해서 좋은 custom hook의 예리를 추가로 정리해보았다. 

 

좋은 custom hook 예시

1. Enter / Esc key hook

특정 버튼이 클릭하고 클릭하지 않는 것에 대해 핸들링하는 부분의 로직이 길어졌는데 아래 hook을 통해 로직을 줄일 수 있고 또 다른 상태와 state 역시 겹치지 않는 다는 점에서 고유성을 지킨 예시인 것 같다. 

import { useEffect } from 'react';

interface IUseEnterEscButtonsProps {
    handleCancel: Function,
    handleConfirm: Function,
}
export const useEnterEscButtons = ({handleCancel, handleConfirm}:IUseEnterEscButtonsProps)=>{
    useEffect(()=>{
        const listener = (event: {code:string, preventDefault: ()=>void}) => {
            if((event.code === 'Enter' || event.code === 'NumpadEnter')){
                handleConfirm();
                event.preventDefault();
            }
        }
        document.addEventListener('keydown', listener);
        return () => {
            document.removeEventListener('keydown', listener);
        }
    },[handleConfirm]);

    useEffect(()=>{
        const listener = (event:{code:string, preventDefault:() => void}) => {
            if(event.code === 'Escape'){
                handleCancel();
                event.preventDefault();
            }
        }
        document.addEventListener('keydown', listener);

        return () => {
            document.removeEventListener('keydown', listener);
        }
    },[handleCancel]);
}

2. debounce hook

debounce는 여러번의 요청이 발생하는 경우 모든 요청을 그대로 처리하는 것이 아니라 요청의 마지막에서 특정 시간이 지난 후 하나의 요청만 보내는 것으로 resizing이나 검색어 추천등에 사용된다. 아래 hook은 input의 onChange 등의 이벤트가 있는 경우 어느정도 요청이 을 보낸 후 마지막 요청을 보냈을 때 delay 타임 이후 요청한 값에 대한 처리를 시작한다. 

interface IUSerDebounce {
    value:any,
    delay:number
}
const UseDebounce = ({value, delay}:IUSerDebounce) => {
    const [debouncedValue, setDebouncedValue] = useState(value);
    useEffect(()=>{
        const handler = setTimeout(()=>{
            setDebouncedValue(value)
        }, delay)
        return () => {
            clearTimeout(handler);
        }
    }, [value, delay])

    return debouncedValue;
}

3. resizing hook

반응형을 width에 따라 구현해야 하는데 라이브러리를 사용하는 것도 좋지만 의존성을 줄이기 위해 아래와 같이 간단하게 custom hook을 작성해서 사용할 수 있다. 

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

const useWindowResize = () => {
    const [width, setWidth] = useState(window.innerWidth);
    const [height, setHeight] = useState(window.innerHeight);

    const listener = () => {
        setWidth(window.innerWidth);
        setHeight(window.innerHeight);
    }

    useEffect(()=>{
        window.addEventListener(
            "resize", listener
        )
        return () => {
            window.removeEventListener("resize", listener)
        };
    },[])
    return {
        width,
        height
    }
}
            

export default useWindowResize;

 

 

혼자 개발을 한다면 모르겠지만 협업을 하는 과정에서 custom hook은 그 사용에 의한 결과를 잘 생각해보고 사용해야겠다는 생각을 하게 된다. 단순히 깔끔해보이려고 사용했다기 보다는 정확히 custom hook을 사용할 수 있는 지점들이 있을 것이라 보인다. 자주 사용해 보면서 최적으로 사용할 수 있는 지점에 대한 감을 익혀봐야겠다. 

 

 

 

아래의 내용을 참고했습니다.

https://betterprogramming.pub/react-custom-hooks-with-real-life-examples-c259139c3d71