개발/프론트엔드 (JS & React)

[React Hooks] Custom Hook examples 커스텀 훅 예제

jungwon_ 2022. 9. 27. 18:50

리액트 훅에는 useState, useEffect, useMemo 등이 있는데, 리액트에서는 이러한 기본 훅 이외에도 커스텀 훅을 만들 수 있다.

 

리액트 공식 문서에서는 커스텀 훅을 사용하면 컴포넌트 로직을 재사용 가능한 함수로 만들 수 있다고 소개하고 있다.

 

그렇다면 커스텀 훅은 언제 사용되고 어떤 예제가 있는지 알아보자.


useFetch

출처

 

프론트엔드를 개발할 때 서버로부터 데이터를 받아와 화면에 뿌려주는 것은 누구나 해봤을 것이다.

 

이 때 가장 흔한 코드 패턴은 useState를 사용해 `data` state를 선언하고 useEffect를 사용해 컴포넌트가 처음 렌더링될 때 fetch를 사용해서 데이터를 받아오고 그 값을 `data` state에 업데이트하는 것이다.

const App = () => {
  const [data, setData] = useState(null);

  useEffect(() => {
    fetch(resource)
      .then((res) => res.json())
      .then((data) => setData(data));
   }, []);
}

그런데 만약 이런 로직을 여러 컴포넌트 혹은 state에서 반복한다면 재사용 가능한 함수로 분리하는 것이 좋다.

 

그러면 어떻게 커스텀 훅을 만들 수 있을까?

 

우선 함수명을 `use`로 시작하는 이름으로 선언한다. 그래야만 리액트가 커스텀 훅이라는 것을 알 수 있기 때문이다.

 

그런 다음 기존 훅을 배합하여 새로운 훅을 만들 수 있다. useFetch 예제는 아래와 같다.

// useFetch.js
import { useState, useEffect } from "react";

const useFetch = (url) => {
  const [data, setData] = useState(null);

  useEffect(() => {
    fetch(url)
      .then((res) => res.json())
      .then((data) => setData(data));
  }, [url]);

  return [data];
};

export default useFetch;
// App.js
import useFetch from './useFetch';

const App = () => {
  const [data] = useFetch(url);
}

이 코드는 위 예제와 같은 로직이지만 커스텀 훅을 사용해 컴포넌트 코드가 좀 더 깔끔해진 것을 볼 수 있다.


useStorage

출처

 

또 다른 예제를 살펴보자.

 

내가 개인적으로 fetch만큼 자주 사용하는 로직은 state가 변경될 때마다 session 혹은 local storage에 업데이트하는 로직이다.

 

예제를 위해 input값을 받아 state가 변경될 때마다 로컬 스토리지에 저장하고, 새로운 페이지를 불러올 때 로컬 스토리지에 저장된 값이 있으면 그 값을 불러오도록 한다고 가정해보자.

 

리액트 기본 훅을 사용하면 아래와 같이 구현할 수 있다.

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

function App() {
  const [value, setValue] = useState('')
  
  useEffect(() => {
    const savedValue = JSON.parse(localStorage.getItem('myKey'))
    if (savedValue) setValue(savedValue)
  }, [])
  
  useEffect(() => {
    localStorage.setItem('myKey', JSON.stringify(value));
  }, [value]);
  
  return (
    <input
      type="text"
      value={value}
      onChange={e => setValue(e.target.value)}
    />
  )
}

이제 위 코드에서 useStorage 커스텀 훅을 만들어 분리해보자.

// useStorage.js
import { useState, useEffect } from 'react'

function getSavedValue(key, initialValue) {
  const savedValue = JSON.parse(localStorage.getItem(key))
  if (savedValue) return savedValue
  
  if (initialValue instanceof Function) return initialValue()
  return initialValue
}
export default function useStorage(key, initialValue) {
  const [value, setValue] = useState(() => { // 컴포넌트를 불러올 때만 실행한다.
    return getSavedValue(key, initialValue)
  })
  
  useEffect(() => {
    localStorage.setItem(key, JSON.stringify(value))
  }, [value])
  
  return [value, setValue]
}
// App.js
import React from 'react'
import useStorage from './useStorage';

function App() {
  const [value, setValue] = useStorage('myValue', '')
  
  return (
    <input
      type="text"
      value={value}
      onChange={e => setValue(e.target.value)}
    />
  )
}

첫 번째 예제와 마찬가지로 컴포넌트 코드가 훨씬 간결해졌다.


리액트의 기본훅과 마찬가지로 커스텀 훅 또한 같은 훅을 여러 state나 component에서 써도 상태를 공유하지 않는다.

 

즉 const [data1] = useFetch(url1)과 const [data2] = useFetch(url2)를 사용한다고 해서 data1과 data2가 연동되는 것이 아니라는 뜻이다.

 

useState로 선언된 모든 state가 각자 다른 상태를 가지듯 커스텀 훅도 마찬가지다.

 

위 예제 외에도 커스텀 훅의 사용법은 무궁무진하니 여러 번 반복되는 컴포넌트 로직을 발견하면 커스텀 훅을 만들어 분리하도록 하자.


출처

- useFetch : React Custom Hooks - W3Schools

- useStorage: The Power Of Custom Hooks - WebDevSimplified Blog