Menu

리액트 커스텀 훅: 직접 만드는 법과 예제

커스텀 훅은 이름이 use로 시작하고 다른 훅을 호출하는 함수로, 여러 컴포넌트가 같은 상태 로직을 재사용할 수 있게 해 줍니다. useToggle, useLocalStorage, useDebounce, useWindowWidth를 만들어 보고, 호출마다 자기만의 state를 받는 이유를 배웁니다.

이 페이지에는 실행 가능한 에디터가 있습니다 - 편집하고 실행하면 결과를 바로 볼 수 있습니다.

커스텀 훅은 이름이 use로 시작하고 다른 훅을 호출하는 함수입니다. 두 컴포넌트가 같은 상태 로직을 필요로 할 때 작성합니다. useState와 useEffect 호출을 함수로 옮기고, 컴포넌트에 필요한 것을 반환하고, 각 컴포넌트에서 그 함수를 호출합니다.

두 스포일러 모두 useToggle을 호출하는데, 하나를 열어도 다른 하나는 닫혀 있습니다. 호출마다 자기만의 useState가 실행되므로 각 컴포넌트는 자기만의 값을 받습니다. 커스텀 훅은 state가 아니라 로직을 공유합니다.

커스텀 훅 작성 규칙

  • 이름은 use와 대문자로 시작합니다: useToggle, useOnlineStatus. 훅 린트 규칙은 이 접두사로 훅 호출을 찾아 검사합니다.
  • 최소 하나의 훅을 호출합니다. 훅을 호출하지 않는 도우미는 일반 함수입니다. 조건문 안을 포함해 어디서든 호출할 수 있도록 useFormatDate가 아니라 formatDate로 이름 지으세요.
  • 훅의 규칙을 따릅니다: 훅은 그 함수의 최상위에서 호출하고, 그 함수는 컴포넌트나 다른 커스텀 훅에서만 호출합니다.
  • 유용한 것은 무엇이든 반환합니다. 호출하는 쪽에서 값 이름을 바꿀 때는 배열([on, toggle])이 편리하고, 세 개 이상이라면 객체({ data, loading, error })가 더 읽기 쉽습니다.

useLocalStorage: 새로고침해도 남는 state

useState는 새로고침하면 모든 것을 잊어버립니다. 이 훅은 시작 값을 localStorage에서 읽고 모든 변경을 다시 씁니다.

미리보기는 브라우저가 localStorage를 막는 샌드박스 프레임에서 실행되므로, 여기서는 Console에 경고가 보이고 훅은 일반 state로 대체됩니다. 일반 페이지라면 이름이 새로고침 후에도 남습니다. try/catch는 이 미리보기만을 위한 것이 아닙니다. 시크릿 모드, 차단된 쿠키, 가득 찬 저장 용량 모두 실제 앱에서도 저장소가 오류를 던지게 합니다. useState에 함수를 넘기면 저장소는 첫 렌더링에서 한 번만 읽힙니다.

useDebounce: 사용자가 입력을 멈출 때까지 기다리기

검색창은 키를 누를 때마다 요청을 보내면 안 됩니다. useDebounce는 정해진 시간 동안 바뀌지 않았을 때만 업데이트되는 값의 사본을 반환합니다.

단어를 빠르게 입력해 보세요. Typed는 모든 키를 따라가고, Debounced는 입력을 멈추고 0.5초 뒤에 따라잡으며, Console에는 검색 한 번이 기록됩니다. 키를 누를 때마다 value가 바뀌므로, React는 새 타이머를 시작하기 전에 클린업(clearTimeout)을 실행합니다. 500을 0으로 바꾸면 키를 누를 때마다 검색이 따로 기록됩니다.

useWindowWidth: 클린업과 함께 구독하기

브라우저를 듣는 훅은 컴포넌트가 사라질 때 리스너를 제거해야 합니다. 그렇지 않으면 마운트할 때마다 리스너가 하나씩 남습니다.

브라우저 창 크기를 바꾸면 숫자가 따라갑니다. 버튼을 클릭하면 Console에 리스너가 제거되는 것이 표시됩니다. 이펙트에서 return을 지우고 몇 번 마운트했다가 언마운트해 보세요. 매번 added 줄은 나오지만 removed는 없으므로 이전 리스너가 쌓입니다. 이런 브라우저 값에는 React가 바로 이 일을 위해 만든 useSyncExternalStore가 있으며, 커스텀 훅 안에서 호출하기 좋습니다.

로직은 공유되고, state는 공유되지 않습니다

같은 훅을 호출하는 두 컴포넌트는 그 안의 모든 것을 따로따로 받습니다. 훅이 전역 변수처럼 동작하리라 기대하는 사람들은 이 점에 놀랍니다.

어느 버튼을 클릭해도 그 카운터만 움직입니다. 둘 다 같은 숫자를 보여 줘야 한다면 state를 App으로 옮겨 아래로 넘기거나, 컨텍스트에 두세요. 커스텀 훅이 편의를 위해 그 공유 state를 감쌀 수는 있지만(CartContext를 읽는 useCart()), 공유는 훅이 아니라 컨텍스트에서 옵니다.

커스텀 훅을 작성해야 할 때

같은 훅 호출 묶음이 두 컴포넌트에 나타날 때, 또는 한 컴포넌트의 이펙트 로직이 이름을 붙여야 더 잘 설명될 만큼 길 때 훅으로 추출하세요(useOnlineStatus()가 리스너 스무 줄보다 읽기 쉽습니다). 이름만 바꾸려고 useState 호출 하나를 훅으로 감싸지 말고, props로 state를 설정하려고 useEffect만 호출하는 훅도 작성하지 마세요. 그런 값은 대신 렌더링 중에 계산하세요.

자주 묻는 질문

React에서 커스텀 훅이란 무엇인가요?

이름이 use로 시작하고 하나 이상의 다른 훅을 호출하는 자바스크립트 함수입니다. 상태 로직(state, 이펙트, ref)을 묶어 두어서, 어떤 컴포넌트든 그 함수를 호출해 재사용할 수 있습니다.

커스텀 훅은 컴포넌트 사이에서 state를 공유하나요?

아니요. 커스텀 훅을 호출할 때마다 그 안의 useState와 useEffect 호출이 따로 실행되므로, 각 컴포넌트는 독립적인 state를 받습니다. 컴포넌트 사이에서 같은 값을 공유하려면 state를 끌어올리거나, 컨텍스트나 외부 스토어에 두세요.

커스텀 훅의 이름은 왜 use로 시작해야 하나요?

그 이름이 React의 린트 규칙과 다른 개발자에게 이 함수가 훅을 호출한다는 것을 알려 주므로, 훅의 규칙이 적용됩니다. 최상위에서 호출하고, 조건문이나 반복문 안에서는 절대 호출하지 않는 것입니다. 접두사가 없으면 린터가 그 안의 호출을 검사할 수 없습니다.

커스텀 훅이 JSX를 반환해도 되나요?

무엇이든 반환할 수 있지만, JSX를 반환한다면 보통 컴포넌트로 작성하는 편이 낫습니다. 커스텀 훅은 일반적으로 값과 함수를 배열([value, toggle])이나 객체({ data, error, loading })로 반환합니다.

useEffect나 이벤트 핸들러 안에서 커스텀 훅을 호출할 수 있나요?

아니요. 커스텀 훅도 훅이므로 훅의 규칙을 따릅니다. 컴포넌트나 다른 커스텀 훅의 최상위에서 호출하세요.

Coddy 프로그래밍 언어 일러스트

Coddy로 코딩 배우기

시작하기