상세 컨텐츠

본문 제목

React(리액트) 티키타카 39 커스텀 훅(Custom Hook)

React

by e7e 2026. 1. 19. 15:06

본문

참으로 끔찍하겡 달가운 이야기당.

커스텀 훅을 잘 써야 되는 게 아니야요?~~

 

그렇당 잘 써야 할 꺼이당. 

컴포넌트 내부 소스를 간단히 하기 위해서도, 더불어 가독성을 올려드리고,

재 사용 가능하게 맹글어서, 이 컴포넌트 저 컴포넌트에서도 널리 재활용

가능하게 하기 위해서도.. ( 현실에서도 재활용은 쓰레기를 줄여준당!)

 

사람도 일회성이 아니고, 재활용이 가능했으면 하는 무서운 바람이 분당.

 

Custom Hook을 만드는 건 너무나 간단하지만

좋은 재사용 유용한 Custom Hook을 만드는 일은 그리 간단하지는 않당.

 

개인적인 추천은 컴포넌트를 만들다 보면, 컴포넌트 내부(특히 JSX 리턴 앞부분)의

JS 소스가 많아지면, 보기가 조금씩 힘들어지기 시작하는데, 이것을 커스텀 훅으로

별도 파일로 빼내보면 은근 슬쩍 반복 되는 게 보일 수 있는데, 바로 이때~~

반복성이 눈과 뇌리를 찔렀을 때!, 재 사용성까지 고민하여 유용한 커스텀 훅으로

리팩토링에 도전하면 꽤나 수확이 결코 적지 않지 하지 아니 할 지어다.

 

SPA(Single Page Application)의 경우, 비동기 AJAX의 사용이 대표적으로

많을 수 밖에 없으닝, 여기에 대표적 커스텀 훅 케이스가 존재 할 지어랑.

 

말은 아무리 떠들어야 뇌리에 이미지를 잘 맹글지 못하닝,

코드 기반으로 눈과 뇌리에  전기 지지미 자극을 흘려보장.

 

vite를 이용하여 React 프로젝트를 만드는 건 그냥 알아서 맡기겠당.

[ 참고로 난 재미를 위해 tailwindcss도 설치하고야 말았당. ]

 

그럼 목적지를 ajax(여기선 fetch)를  이용한 커스텀 훅으로 정하고 달리장

 

Idol.jsx

const imgServer = "https://newsimg.sedaily.com";

function Idol({ name, role, imgURL }) {
  return (
    <div className="w-full text-center border-8 border-pink-500 rounded-2xl">
      <h1 className="text-4xl mt-2 mb-2 font-extrabold">{name}</h1>
      <h2 className="text-2xl mb-2 text-blue-600 font-bold bg-gray-300">
        {role}
      </h2>
      <img
        src={`${imgServer}${imgURL}`}
        className="w-full h-120 rounded-[50%]"
      />
    </div>
  );
}

export default Idol;

 

IdolList.jsx

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

const e7eURL = "https://my-json-server.typicode.com/jangmk/kpop/agents";

function IdolList() {
  const [idols, setIdols] = useState([]);

  useEffect(() => {
    let totalIdols = [];
    const getIdols = async () => {
      const response = await fetch(e7eURL);
      const data = await response.json();

      // 데이터 축출
      data.forEach((ent) => {
        ent.groups.forEach((group) => {
          totalIdols = [...totalIdols, ...group.members];
        });
      });

      console.log("체에킁: ", totalIdols);
      setIdols(totalIdols);
    };
    getIdols();
  }, []);

  return (
    <div className="w-[80%] pt-40  mx-auto grid grid-cols-4 gap-4 ">
      {idols.map((idol) => (
        <Idol key={idol.name} {...idol} />
      ))}
    </div>
  );
}

export default IdolList;

 

App.jsx

import IdolList from "./IdolList";

function App() {
  return (
    <>
      <h1 className="fixed w-full text-center text-8xl bg-black text-white p-5 mb-3 ">
        지원 더 없나용
      </h1>
      <IdolList />
    </>
  );
}

export default App;

 

아마도 아래 비스무리 화면이 보였을거당. (요즘 웬디 노래가 좋당)

 

하지만 관심사는 결코 결과가 아니었당. 그랬당

IdolList.jsx에서 useEffect Hook에 사용한 fetch당.

 

비동기 AJAX를 가마니 쓰고 앉아서  쪼금 가마니 생각해 보면

결과가 나온 성공 케이스 (이때 결과를 data라 하장)

에러가 발생한 실패 케이스 (이때 결과를 error라 하장)

그리고 아직 성공/실패를 알수없는 진행 케이스(이때를 pending이라 하장)

이 3가지를 기본적으로 담아야 한다.

기본적으로 담아야 한다는 말은 해당 코드가 반복될거예요와 같은 의미당.

바뀌는 건 url 일 꺼시당.  (메소드 부분은 일단 머리에서 제외, 상황 간단히)

[ 사실 Promise는 이 3가지 상태를 기준으로 만들어졌당 ]

 

바로 맹글어서 적용해 보장.~~

useFetcher

import { useEffect, useState } from "react";

// 대표적 커스텀 훅(AJAX)
function useFetcher(url) {
  // 3가지 상태를 위한 상태변수, Re-Redering을 발생시키기 위함
  const [data, setData] = useState(null); // 결과가 있는지 없는지
  const [pending, setPending] = useState(true); // 실행중인지 끝났는지
  const [error, setError] = useState(null); // 에러가 있는지 없는지

  useEffect(() => {
    const fetchData = async () => {
      try {
        setPending(true); // 아작스 들어가기 전에 실행중 표시, Re-Rendering
        const response = await fetch(url);
        if (!response.ok)
          throw new Error(`Http Error! Status: ${response.status}`); //  강제 Error발생 -> catch로 이동
        const result = await response.json();
        setData(result);
      } catch (error) {
        setError(error.message); // Re-Rendering
      } finally {
        setPending(false); // 그냥 초기화 그래도 Re-Rendering 발생
      }
    };

    fetchData(); // 호출

    // 클리어 함수 (unmount 되었을 때 실행)
    return () => {
      // 혹 필요한 작업이 있다면
    };
  }, [url]); // url이 바뀌면 실행됨

  return { data, pending, error }; // 이 훅을 가져다 쓸 때, 꺼내 쓸 수 있도록
}

export default useFetcher;

 

바뀐 IdolList.jsx

import { useEffect, useState } from "react";
import Idol from "./Idol";
import useFetcher from "./useFetcher";

const e7eURL = "https://my-json-server.typicode.com/jangmk/kpop/agents";

function IdolList() {
  const { data, pending, error } = useFetcher(e7eURL);

  if (pending) return <h1 className="text-4xl">땀시만 기다려주삼</h1>;
  if (error) return <h1>{error}</h1>;

  let totalIdols = [];
  data.forEach((ent) => {
    ent.groups.forEach((group) => {
      totalIdols = [...totalIdols, ...group.members];
    });
  });

  return (
    <div className="w-[80%] pt-40  mx-auto grid grid-cols-4 gap-4 ">
      {totalIdols.map((idol) => (
        <Idol key={idol.name} {...idol} />
      ))}
    </div>
  );
}

export default IdolList;

 

결과는 분명 같지 않지 아니하지 않을 수 없지 않음을 본능이 느낀당..

느끼미 친구가 함께 왔다면 더 좋을거당. ~~

 

이 형태에 매력을 느낀 사람들이 더욱 발전시킨 react-query란 걸 만들었당.

당연히 그러할 거시당. React에도 역사란 게 있으닝..  

아래는 훌륭한 React-Query의 사용법이당. 

[분명 맘에 들꺼이당. useEffect가 눈에 보이지 않으닝 좋을 수 밖에~~ (><)]

2025.04.08 - [React] - React(리액트) 티키타카 34 (@tanstack/react-query)

 

분명 천천히 꾸준히 React를 사용한다면 당신도 이런 걸 만들 생각에 

빠지게 될 것이당.  불편함이 모여서 어깨를 짓 누르면 사람은 생각한당.

벗어날 방법을....  당신도 사람이 되어야한당.

 

 


 

관련글 더보기