리액트 커스텀 훅을 만들어보자.

개발자 베니·2022년 1월 21일
26
post-thumbnail

안녕한가!

개요

커스텀 훅의 장점들과 어떻게 사용하면 좋을지에 대한 것들을 정리해볼 생각이다. 일단 이 과정에서 늘 그렇듯 왜 커스텀 훅이 나왔는지, 쓰면 뭐가 좋은지, 어떤 식으로 사용하는게 좋은지 정도에 대해서 설명을 해보려고 한다. 사실 커스텀 훅은 상당히 재밌는 부분이다. 관련해서 소개해주고 싶은 내용도 많고 얻어갈 수 있는 키워드도 많을 것이다. 이 글을 다 읽고 한번 자신만의 커스텀 훅을 만들러 가보는 것도 좋은 생각이다. 개발 능력을 키울때 문서를 읽는 것은 더하기고 직접 만드는 것은 곱하기며, 직접 만들고 기록하면 제곱이라고 생각한다. 아무튼 시작해보겠다.

Custom Hooks

우선 커스텀 훅의 핵심 키워드는 반복되는 로직이다. 반복되는 로직은 즉 재사용을 의미한다. 커스텀 훅을 왜 사용할까를 누군가 물어본다면 "반복되는 로직을 하나로 묶어 재사용하기 위함이다" 라고 대답해도 무방하다. 잠깐 공식 문서에 적혀있는 내용을 보자.

개발을 하다 보면 가끔 상태 관련 로직을 컴포넌트 간에 재사용하고 싶은 경우가 생깁니다. 이 문제를 해결하기 위한 전통적인 방법이 두 가지 있었는데, higher-order components와 render props가 바로 그것입니다. Custom Hook은 이들 둘과는 달리 컴포넌트 트리에 새 컴포넌트를 추가하지 않고도 이것을 가능하게 해줍니다. - 리액트 공식 문서-

공식 문서에 따르면 전통적인 방법 두 가지가 나오는데 간단히 설명하자면 상위 컴포넌트에서 하위 컴포넌트에 프롭스를 전달하는 느낌으로 생각하고 넘어가면 된다. 그렇다면 전통적인 방법과 커스텀 훅의 차이점은 무엇일까? 바로 커스텀 훅은 컴포넌트 트리를 통해 전달되어 생성하는 것이 아닌 각 컴포넌트에서 독립적으로 state를 생성한다.

더 쉽게 말하자면 부모님이 보증금 내줘서 자취방 구하면 사실상 부모님 자취방에 내가 사는 건데 내가 돈 벌어서 보증금 내고 자취방 들어가면 내 자취방이라는 독립적 공간이 생기는 것과 비슷하다고 보면 된다. 근데 솔직히 파고들면 오류가 많을 것 같은 예시니까 독립적인 사용이 가능하다는 키워드만 알고가도 된다.

잠깐 정리를 해보자.

1. 왜 커스텀 훅을 사용하나요?

 커스텀 훅은 반복되는 로직을 묶어 재사용하기 위해 사용한다.

2. 커스텀 훅을 만들었을 때 반환된 값이나 기능의 상태는 어떻게 되나요?

 커스텀 훅을 사용해서 만든 기능은 각 컴포넌트에서 독립된 상태를 가진다.

지금 이 두 가지를 설명한 것이다. 그러면 기본적으로 어떻게 커스텀 훅을 만드는 지 확인해보자.

사용법

사용법을 좀 더 쉽게 이해할 수 있도록 간단한 예시와 코드를 이용해서 설명을 해볼 것이다. 우선 예시부터 들어보자. 나는 독자적인 개발 블로그를 운영하고 있는데 하루에 10,000명씩 들어와서 내 글을 보고 간다고 하자. 어느날 어떤 광고주가 나에게 블로그에 광고를 띄우고 싶다고 했다.

최대한 많은 사람에게 노출시킬수록 돈이 많이 들어온다는 것을 알게된 나는 유저가 어떤 페이지에 들어가건 오른쪽 상단에서 광고가 화면밖에서 튀어나온 후에 몇 초후에 다시 들어가는 형태의 컴포넌트를 제작했다.

근데 너무 많이 띄우면 미안하니까 광고 제거를 누르면 그 유저에게는 당분간 안 보이게끔 설정해놓는 훅을 만들었다. 이걸 한번 코드로 적어보자!

import { useState } from "react";

function useAdOnOff(initialForm) {
  const [userAdOn, setUserAdOn] = useState(initialForm);

  const 유저가_광고_제거를_눌렀는_지_서버에서_확인하는_대충_복잡한_작업 = () => {

      ...확인중

      if (광고제거함) return true
      else if (광고제거안했음) return false
  }

  return 유저가_광고_제거를_눌렀는_지_서버에서_확인하는_대충_복잡한_작업();
}

export default useInputs;

자 내가 직접 만든 커스텀 훅이다. 대충 복잡한 작업을 거친 뒤에 광고 제거를 했다면 true를 리턴하고 아니면 false를 리턴한다.

이게 뭐냐 싶겠지만 사실 커스텀 훅을 만드는 것은 별거 없다. 그냥 적당한 위치의 폴더에 원하는 파일을 만들고 그 안에서 Hooks의 API들을 이용해서 원하는 기능을 만들면 된다.

참고로 Hooks의 API는 우리가 주로 사용하는 useState, useEffect나 useCallback, useReducer 같은 것들을 말한다. 그리고 내가 만든 멋진 useAdOnOff도 Hooks의 API가 됐다.

    import React from 'react';
    import useAdOnOff from './hooks/useAdOnOff';
    import 광고입니다 from "./components/ad";

    const 여러가지_페이지_중_하나 = (...) => {
        const [isAdDelete] = useAdOnOff(props.user.id);

        return (
            <div>
                <광고입니다 isUserAdDelete={isAdDelete} />
            ...
            </div>
        );
    };

    export default 여러가지_페이지_중_하나;

이 코드는 내 블로그를 구성하는 여러가지 페이지 중에 하나다. 이제 나는 화면 밖에서 광고를 보여주기 위해서 나타나는 광고입니다 컴포넌트를 불러와서 이 유저가 광고 제거를 했는지 안했는지 확인해주는 커스텀훅과 연계해서 쓰기만 하면 된다.

만약 isAdDelete가 false면 유저가 광고를 제거 안했기 때문에 광고입니다 컴포넌트에 false가 프롭스로 전달되고, 내부적으로 구현된 코드로 인해 광고를 띄운다.

isAdDelete가 true면 유저가 광고를 제거 했기 때문에 광고입니다 컴포넌트에 true가 프롭스로 전달되고, 내부적으로 구현된 코드로 인해 광고를 띄우지 않는다.

개인적으로 모든 예시는 극단적으로 보면 이해가 빠른 것 같다. 저 여러가지 페이지가 만약에 1,000개 정도 있다고 생각해보자. 커스텀 훅 안 썼으면 전통적인 방법으로 각각에 프롭스로 전달을 해주던가 아니면 하나하나 페이지에 직접 길게 작성을 해야 했을 텐데, 커스텀 훅을 사용하니 결과적으로 각 페이지 당 두 줄이면 충분해졌다.

저 코드는 이해를 돕기 위해서 간단한 예시 코드다.
과몰입하면서 적었더니 개인적으로 생각하기에 멋진 코드가 나왔다.
물론 당신까지 과몰입하면 안 된다.

어쨌든 말하고 싶은 요지는 커스텀 훅은 만들기가 아주 간단하고 쉽다는 것이다. 그리고 어떤 경우에 어떻게 만드냐에 따라서 재사용성이 크게 높아질 수 있다. 어떤 기능을 만들 것 인지, 어떤 값을 반환할 것 인지 모두 개발자가 하고 싶은데로 할 수가 있다는 것이다. 정말 자유롭고 재밌지 않은가!

어떻게 사용해볼까?

자유롭고 쓰기 편한 녀석들은 늘 주의해서 다뤄줘야 하는 법이다. 내가 아직 실무에서 일하고 있는 것은 아니지만, 몇 개의 개발자 컨퍼런스와 직접 사용하면서 느낀 점들을 좀 다뤄볼까 한다. 이 내용은 클린 코드와 직접적으로 연결이 되며, React Hooks가 가지는 선언적인 API들의 장점을 끌어올리기 위한 내용이기도 하다. 시작해보겠다!

자 커스텀 훅을 위에서 잘 보고 왔다면 사용성은 개발자가 원하는 만큼 높아진다.

우선적으로 커스텀 훅을 가장 잘 사용하는 방법은 많이 반복되는 로직을 하나로 묶어서 재사용하는 것이다. 1,000줄의 코드를 필요한 페이지에 모두 넣는 것이 아닌 따로 분리해서 만든 다음 호출해서 사용하는 것. 이렇게 사용할 수 있다면 이미 커스텀 훅을 잘 사용하고 있다고 볼 수 있다.

그런데 이렇게 재사용되는 경우가 아니면 커스텀 훅을 사용하지 않는 것이 좋을까?

이는 현재 코드의 상황을 보면서 확인을 해봐야한다. 내 기준에서는 반복되는 로직이 아니어도 사용할 수 있다.

"아니 왜 코드를 분리합니까? 반복 안 되면 그냥 그 페이지에서 직접 useState나 Hooks의 API들을 이용해서 바로 만드는 것이 좋을 것 같습니다."

솔직히 이 말도 틀린 말은 아니다. 나는 여기서 내가 작성한 글 중에 클린 코드에 관한 내용을 가져와서 설명을 해보려고 한다. 우선 우리의 코드는 당연하게도 한 가지 기능만 하는 경우가 있고, 다양한 기능을 해야할 때가 있다. 혹은 n개의 기능을 하는 코드에 기능 추가를 위해서 다시 오는 경우도 있을 것이다. 그에 따라 사용하는 변수와 함수는 계속해서 늘어날 것이고, 필연적으로 코드는 이전보다 지저분해진다.

이런 경우를 해결하기 위해서 페이지 내에서 적절한 함수와 함수이름을 사용해서 하나의 함수가 하나의 기능을 할 수 있게끔 분리해서 해결할 수도 있다. 여기서 잠깐 우리 리액트가 가지는 특징 한 가지를 짚고가보자. 리액트는 선언형 프로그래밍이 가능한 프레임 워크다.

선언형 프로그래밍

선언형 프로그래밍은 무엇인가? 모든 코드를 굳이 보지 않아도 내부에서 잘 구현해놨다면 믿고 사용할 수 있는 것이다.

    import React from 'react';
    import useAdOnOff from './hooks/useAdOnOff';
    import 광고입니다 from "./components/ad";

    const 여러가지_페이지_중_하나 = (...) => {
        const [isAdDelete] = useAdOnOff(props.user.id);

        return (
            <div>
                <광고입니다 isUserAdDelete={isAdDelete} />
            ...
            </div>
        );
    };

    export default 여러가지_페이지_중_하나;

위의 예시에서 잠깐 보자면 어느 부분이 선언형 프로그래밍일까? 대표적으로는 광고입니다 컴포넌트가 있고, 내가 만든 커스텀 훅인 useAdOnOff와 리턴값인 isAdDelete도 선언적인 표현이라고 볼 수 있다. 모르는 사람이 봤을 때 광고입니다는 누가봐도 안 쪽 코드에 광고에 관련된 코드가 들어가 있을거라고 생각할 수 있다.

isAdDelete는 정확히 알 순 없지만 아마도 광고 제거와 연관이 있다고 추측할 수 있다.

이 중에서 useAdOnOff는 코드 내부에서 단 한 가지 기능만을 한다. 바로 유저가 광고 제거를 눌렀는지 안 눌렀는지 확인하는 기능이다.

이렇게 겉만 보고 무엇인지 추측할 수 있는 것을 개념적으로 설명하면 추상화 작업이라고 한다. 광고입니다 컴포넌트는 추상화가 아주 잘된 편에 속한다. 누가봐도 광고라고 알 수 있기 때문이다.

실제로 위의 예시에서 광고입니다 내부 코드를 예시로도 작성하지 않았는데 누가봐도 광고에 관련된 코드겠거니 싶지 않은가!

지금 내가 하고싶은 말은 커스텀 훅은 굳이 재사용되지 않아도 사용할 수 있다는 것이다. 대신 이렇게 사용을 하려면 누가봐도 합당한 이유가 필요하다. 개발자는 항상 Why? 라는 질문에 자신이 판단해서 나오는 대답을 할 수 있어야 한다.

나에게 "왜 이 부분을 분리했나요?" 라는 질문이 들어온다면

"기능 추가를 하기 위해서 페이지에 직접 기능을 추가해봤으나, 코드의 응집도가 낮고 함수가 단일 기능을 하지 못하는 등 추후에 버그가 생길 우려가 있고 유지보수 시간이 결과적으로 더 높아질 것이라고 판단해서 커스텀 훅을 만들어 높은 레벨의 추상화 작업을 진행했습니다."

그러면 대부분은 납득 할 거다. 하지만 이런 대답을 들을 수 있긴 하겠다.

"충분히 이해가 갑니다. 하지만 추상화의 레벨이 높기 때문에 한 눈에 이해하기가 쉽지 않습니다. 레벨을 한 단계 정도 낮춰서 코드를 작성해보는 것은 어떻게 생각하세요?"

개발자간의 코드 리뷰와 소통이 중요한 이유다. 이런 피드백을 참고해서 코드를 리팩토링한다면 더 훌륭한 코드를 작성할 수 있고, 결과적으로 코드는 크게 지저분해지지 않고 유지 보수에 들어갈 시간도 줄일 수 있게 된 것이다.

주의 사항

주의 사항은 사실 위에서 다 얘기했다. 커스텀 훅은 자유로운 만큼 신중해서 작성해야한다.

  • 당신이 추상화를 해서 코드를 작성했다면 추상화의 레벨이 적절하게 잘 이뤄졌는가?
  • 커스텀 훅을 사용하지 않아도 되는 부분에서 조차 사용하지 않았는가?
  • 만들어진 커스텀 훅은 한 가지 기능만을 하고 있는가?
  • 혹시 사이드 이펙트를 일으킬 부분이 있지 않는가? 여러가지 경우의 수를 생각해서 작성하라.

스스로 늘 납득이 되어야 한다. 그냥 작성하는 코드는 누구나 할 수 있지만, 수준 높은 코드는 늘 이유가 들어가 있는 법이다. 시니어와 주니어의 가장 큰 차이도 이유가 있는 코드와 그렇지 않은 코드가 아닐까라는 생각을 하고 있다.

혹시라도 이 글을 다 읽었다면 자신이 작성한 코드를 다시 보자. 납득할 만한 코드인가?

정리

생각보다 내용이 길어졌는데, 이 글을 읽고 뭐라도 얻어갔으면 좋겠다. 적고나서 이것만은 알고 가면 좋을 것 같은 두 가지만 적어보겠다. 하나는 커스텀 훅을 사용하는 이유, 그리고 코드 작성에 늘 납득 할만한 이유가 있어야 한다는 점. 그리고 예시는 예시일 뿐! 내가 만든 예시는 완벽하지 않다는 점을 기억해야 한다. 당신만의 예시를 만들어서 기록해보는 것도 나쁘지 않을 것 같다.

여기까지 커스텀 훅에 관한 내용이다.

2개의 댓글

comment-user-thumbnail
2022년 6월 14일

와..! 덕분에 custom hooks을 왜 써야하는지 자세히 알고갑니다! 감사합니다.

  • 중요하진 않지만, useAdOnOff custom hooks 코드에서 export할때는 useInput으로 되어있더라구요. useAdOnOff을 export하는 게 맞을까요..?
1개의 답글