라이브러리&프레임워크/React

[React] Custom Hook, 커스텀 훅

youngble 2022. 10. 10. 21:24

커스텀 훅이란 ?
말그대로 개발자의 입맛에 맞게 커스텀하여 만든 훅이다.
리액트 내장함수의 useState, useReducer, useEffectuse로 리액트훅처럼 사용할 수 있게 개발자가 커스텀하여 만들어 사용하는 것이다.

커스텀 훅정규함수(regular function)과 달리 재사용가능한 함수(Re-usable functions)에 상태(state)를 설정하는 로직을 아웃소싱 할 수 있다. 쉽게 풀어 말하면 정규함수(regular function)과 달리 커스텀 훅다른 커스텀훅을 포함한 리액트 훅을 사용할 수 있다. 왜냐하면 기존 정규함수에서 리액트 hook을 쓸수없도록 되어있는데 커스텀 훅에서는 사용 가능하기 때문이다.

따라서 useState, UseReducer, useEffect 등을 사용하여 관리하는 리액트 상태state를 활용할 수 있기때문에 커스텀 훅을 통해 다른 컴포넌트에서 사용할 수 있는 로직을 커스텀 훅으로 아웃소싱하여 다양한 컴포넌트에서 호출이 가능하다.
로직 재사용이 가능한 메커니즘(어떤 사물이 어떻게 작동하는지의 원리) 훅 이라는 것이다.

커스텀 훅을 쓰는 이유

예를들어 아래와 같이 두개의 컴포넌트가 있다고 한다면

import { useState, useEffect } from 'react';

import Card from './Card';

const ForwardCounter = () => {
  const [counter, setCounter] = useState(0);

  useEffect(() => {
    const interval = setInterval(() => {
      setCounter((prevCounter) => prevCounter + 1);
    }, 1000);

    return () => clearInterval(interval);
  }, []);

  return <Card>{counter}</Card>;
};

export default ForwardCounter;
import { useState, useEffect } from 'react';

import Card from './Card';

const BackwardCounter = () => {
  const [counter, setCounter] = useState(0);

  useEffect(() => {
    const interval = setInterval(() => {
      setCounter((prevCounter) => prevCounter - 1);
    }, 1000);

    return () => clearInterval(interval);
  }, []);

  return <Card>{counter}</Card>;
};

export default BackwardCounter;
1초마다 증가,감소를 출력해준 화면

useEffect 안에서 componentDidMount에서 setInterval 하여 ForwardCounter 컴포넌트는 1초마다 1씩 증가하고
BackwardCounter 컴포넌트는 1초마다 1씩 감소한다. 증가와 감소라는 차이를 제외하면 똑같은 로직을 갖는 컴포넌트이다.

그렇다면 굳이 이렇게 두개의 컴포넌트로 나눠서 할필요가 있을까? 어떻게하면 재사용할 수 있을까 생각해본다면 커스텀 훅을 만들어서 증가와 감소 부분의 로직만 바꿔 넣어서 재사용할 수 있도록 한다.

만드는 방법/사용하는 방법

위의 예제 코드를 기반으로 커스텀 훅을 만들어 보자. 먼저 hooks이라는 폴더를 생성하고 그 안에 use-counter.js 를 만들어 주었다.

여기서 파일명은 개발자가 정하기 나름이지만 그 js파일안에서 만들게될 커스텀훅은 use로 시작하여 사용해야한다. 필수적으로 지켜야하는 엄격한 규칙이기 때문무조건 지켜야하는 룰이다.

일반적인 함수지만 use를 사용함으로써 리액트에게 커스컴훅임을 알려주고 리액트가 훅의 규칙에 따라 사용하겠다고 보장해주는 것이다.즉 이 커스텀 훅을 내장 훅과 같은 방식으로 쓰겠다는 것이다.

먼저 Forward의 로직에 해당하는 코드를 복사하여 아래와 같이 useCounter 커스텀 훅 로직안에 넣어준다.

const useCounter = () =>{
 // 재사용하려는 로직 추가
 const [counter, setCounter] = useState(0);

  useEffect(() => {
    const interval = setInterval(() => {
      setCounter((prevCounter) => prevCounter + 1);
    }, 1000);

    return () => clearInterval(interval);
  }, []);
  return counter;
}

export default useCounter;

그리고 return 값으로 counter를 반환했는데 커스텀 훅에서는 숫자, 배열[], 객체{}, 함수 ()=>{} 등 무엇이든 반환 할수 있다. 위의 경우 counter 증가, 감소하는 숫자가 필요하므로 counter를 반환한 것이다.

import Card from './Card';
import useCounter from '../hooks/use-counter';
const ForwardCounter = () => {
  const counter = useCounter();

  return <Card>{counter}</Card>;
};

export default ForwardCounter;

그리고 다음과 같이 useCounter()를 호출하여 반환되는 값을 저장할 counter라는 변수를 만들어 초기화 시켜주었고, 기존에 있던 코드들은 삭제해 주었다.

실행결과 위와같이 ForwardCounter 컴포넌트의 로직과 출력이 잘되는걸 확인할 수 있다.
하지만 이렇게 쓰게되면 ForwardCounter 컴포넌트에서만 쓰는 로직이고 BackwardCounter 컴포넌트에서는 재사용할 수 없기때문에
재상용 가능한 형태로 만들어주기로 한다.

이를 위해 인수(argument), 매개변수(parameter)를 받아들이게하여 props를 받는 컴포넌트 함수처럼 매개변수를 받아들일 수 있게 한다.
인자로 넘겨주는 방식은 여러가지 있는데 2가지 방법을 써보도록한다

1번째 방법

const useCounter = (forwards = true) => {
  // 재사용하려는 로직 추가
  const [counter, setCounter] = useState(0);

  useEffect(() => {
    const interval = setInterval(() => {
      if (forwards) { // true일때 증가
        setCounter((prevCounter) => prevCounter + 1);
      } else { // false일때 감소
        setCounter((prevCounter) => prevCounter - 1);
      }
    }, 1000);

    return () => clearInterval(interval);
  }, [forwards]);

  return counter;
};

export default useCounter;

boolean값을 넘겨서 true일때는 증가, false일때는 감소하는 로직을 만들어주기로한다.
이렇게 props로 넘겨주어 사용하는 조건 로직을 추가하였으니 BackwardCounter 컴포넌트도 아래와같이 수정해준다.

import useCounter from '../hooks/use-counter';
import Card from './Card';

const BackwardCounter = () => {
  const counter = useCounter(false);

  return <Card>{counter}</Card>;
};

export default BackwardCounter;

useCounter에 false를 넘겨주었으므로 감소하는 로직의 setInterval을 실행하게 된다.

2번째 방법

공통적으로 들어가는 부분이 setInterval에서 setCounter에 +1 하거나 -1 이다. 따라서 setCounter에 들어갈 콜백함수부분을 인자로 받아서 사용하는 커스텀 훅을 만들어보겠다.

import { useEffect, useState } from "react";

const useCounter = (upAndDown) => {

  const [counter, setCounter] = useState(0);

  useEffect(() => {
    const interval = setInterval(() => {
      setCounter(upAndDown); // props로 넘겨준 콜백 매개변수를 대입
    }, 1000);

    return () => clearInterval(interval);
  }, []);

  return counter;
};

export default useCounter;

useCounter 커스텀 훅으로 넘겨준 upAndDown 이름의 매개변수를 useEffect안에서 사용하는 setCounter의 매개변수로 넘겨주었다.

저자리에 있어야했던 부분이 (prev) => prev+1 이거나 (prev) => prev-1 부분이 되야하므로 ForwardCounter 컴포넌트에는 (prev) => prev+1 를 넘겨주고 BackwardCounter컴포넌트에서는 (prev) => prev-1 를 useCounter 훅의 인자로 넘겨주도록 하는 코드는 다음과 같다.

import { useState, useEffect } from "react";

import Card from "./Card";
import useCounter from "../hooks/use-counter";
const ForwardCounter = () => {
  const counter = useCounter((prev) => prev + 1);

  return <Card>{counter}</Card>;
};

export default ForwardCounter;
import useCounter from '../hooks/use-counter';

import Card from './Card';

const BackwardCounter = () => {
  const counter = useCounter((prev)=>prev-1);

  return <Card>{counter}</Card>;
};

export default BackwardCounter;


이밖에도 다양한 커스텀 훅을 활용할 수 있는 범위가 크지만 이번에는 이해를 위한 단순 예제로 마무리 하기로 한다.