본문 바로가기
카카오_구름/리액트

4기 카카오 프론트 전체 스터디 - 리액트 3주차 (HTTP 요청 + 주요 Hooks 정리)

by 코딩의 세계 2025. 5. 11.

시작하기 전에, hook에 대한 공식 문서 링크는 다음과 같다. 참고하자.

https://ko.react.dev/reference/react/hooks

 

내장 React Hook – React

The library for web and native user interfaces

ko.react.dev


오늘은 hook에 대한 글을 쓰겠다. 

목표

이번 글의 목표는 다음과 같다.

  • fetch + useEffect 조합으로 API 요청 흐름 익히기
  • 주요 Hook (useState, useEffect, useRef) 실습
  • 성능 최적화용 Hook (useMemo, useCallback) 개념 잡기
  • 커스텀 Hook 구조 맛보기 (useInput)

+ 여기에 커스텀 훅을 만드는 기준은 뭘까? (공통 로직 분리 기준) 에 대한 이야기도 진행해 보겠다.


fetch + useEffect 조합으로 API 요청 흐름 익히기

위 조합을 사용하는 것을 이야기하기 전에, 우리가 어떠한 프로덕트를 실제로 만들어 간다면 결국 서버와 데이터베이스가 필요해진다. 그리고 보통 그런 데이터들은 서버에 저장하게 된다.

다른 사용자의 클라이언트는 우리의 서버/데이터베이스에 접근하게 된다.

우리는 프론트엔드에서 리액트를 통한 클라이언트 ui 코드만 작성해 두면 된다. 백엔드는 따로 백엔드 개발자가 코드를 작성한다. 다만, 우리가 할 것은 우리의 백엔드 코드와 소통할 코드를 추가로 작성하는 것이다.

이때, 백엔드와 소통은 http을 통해서 소통하게 된다. 

예시 코드 >

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

function UserList() {
  const [users, setUsers] = useState([])

  useEffect(() => {
    const fetchUsers = async () => {
      const response = await fetch('https://jsonplaceholder.typicode.com/users')
      const data = await response.json()
      setUsers(data)
    }

    fetchUsers()
  }, [])

  return (
    <ul>
      {users.map((user) => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  )
}

export default UserList

간단 설명: 컴포넌트가 처음 렌더링될 때 useEffect로 API 요청을 보내 데이터를 가져오는 코드이다.

백엔드는 위 API 요청을 fetch 함수 (브라우저에서 지원해 주는 자체 함수)를 실행해서 데이터를 주고받게 할 수 있다.

참고로 async는 "비동기 처리"를 하게 만드는 명령어라고 생각하면 된다.

(이에 대한 추가적 설명은 영상 참고)


useEffect, useState 에 대한 hook은 밑에서 추가 설명을 하겠다.

주요 Hook (useState, useEffect, useRef) 실습

일단, 주요 hook의 이론부터 말하자면..


useState

상태를 관리하는 가장 기본적인 훅이다.

  • 컴포넌트 내부에서 값(상태)을 저장하고, 그 값이 바뀌면 자동으로 리렌더링
  • 보통 입력값, 토글 상태, 카운터 등 UI에 직접 영향을 주는 값을 저장함
  • 상태는 비동기로 업데이트되며, 바로 다음 줄에서 바뀐 값을 쓸 수 없음

📌 언제 쓰나?

  • 버튼 클릭 횟수 저장
  • 입력값 (input) 상태 추적
  • 모달의 열림/닫힘 여부 등 UI 상태 관리

useEffect

리액트 컴포넌트의 '부수 효과(side effect)'를 처리할 때 사용한다.

  • API 호출, 구독, 타이머 설정 등 DOM 외부 작업이나 비동기 작업에 적합
  • 렌더링 이후 실행되며, 의존성 배열을 통해 실행 시점을 제어함

📌 언제 쓰나?

  • 컴포넌트가 처음 렌더링될 때 1회 실행 → []
  • 특정 값이 바뀔 때마다 실행 → [count]
  • 컴포넌트가 사라질 때 정리(clean-up) 작업도 가능 → 타이머 해제, 이벤트 제거 등

useRef

DOM을 직접 참조하거나, 렌더링과 관계없는 값을 저장할 때 사용한다.

  • 값이 바뀌어도 리렌더링 되지 않음
  • useRef().current 형태로 접근
  • 이전 상태 기억하기, 포커스 제어, 애니메이션 컨트롤 등에 자주 사용

📌 언제 쓰나?

  • 특정 DOM 요소에 포커스 주기 (inputRef.current.focus())
  • setInterval ID 저장
  • 이전 props나 state 저장해서 비교

이 중에서 일단 useState를 가장 많이 사용할 거고, 그다음 useEffect을 자주 쓸 것이니 위 2개의 문법과 사용법은 알아두길 권장한다.

예시 코드 >

설명: 상태 관리, 생명주기, DOM 참조용 기본 훅들

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

function Timer() {
  const [count, setCount] = useState(0)
  const intervalRef = useRef(null)

  useEffect(() => {
    intervalRef.current = setInterval(() => {
      setCount((prev) => prev + 1)
    }, 1000)

    return () => clearInterval(intervalRef.current)
  }, [])

  return <div>경과 시간: {count}초</div>
}

export default Timer

성능 최적화용 Hook (useMemo, useCallback) 개념 잡기

이제는 성능을 최적화하는 것을 도와주는 hook에 대한 개념을 잡아보자.

useMemo

계산 비용이 높은 값을 캐싱하고 싶을 때 사용한다.

  • 의존성 값이 바뀌지 않는 이상, 이전에 계산한 값을 재사용함
  • 무거운 연산(예: 필터링, 정렬, 수학 계산 등)에서 성능 최적화에 유용

📌 언제 쓰나?

  • 렌더링 때마다 재계산할 필요 없는 값
  • 자식 컴포넌트에 props로 넘기는 값이 복잡할 때
  • 리스트 필터링, 계산 등 반복적으로 발생하는 연산

useCallback

함수 자체를 메모이징(캐싱)해서, 불필요한 재생성을 막아주는 훅이다.

  • 보통 자식 컴포넌트에 함수를 props로 넘길 때 유용함
  • 함수가 매 렌더마다 새로 생성되면 불필요한 렌더링이 발생할 수 있는데, 이를 방지

📌 언제 쓰나?

  • 자식 컴포넌트에 함수를 넘기는데, 불필요하게 자주 리렌더 될 때
  • useEffect 안에서 의존성 배열에 함수를 넣어야 할 때
  • 상태 변경 핸들러가 동일하게 유지돼야 할 때

위 hook은 결국 "캐싱"을 이용해서 성능을 최적화하는 훅이다.

+ 여기에 suspense 라는 로딩 콜백 함수가 있다. 이건 성능 최적화뿐 아니라 사용자에게 더 나은 경험을 제공해 준다.

공식 문서 >

https://ko.react.dev/reference/react/Suspense

 

<Suspense> – React

The library for web and native user interfaces

ko.react.dev

토스에서 만든 라이브러리 >

https://suspensive.org/ko

 

React Suspense를 위한 모든 것 | Suspensive

All in one for React Suspense

suspensive.org


useMemo, useCallback에 대한 예시 코드는 다음과 같다.

import React, { useMemo, useState } from 'react'

function ExpensiveCalculation() {
  const [value, setValue] = useState(1000)
  const [count, setCount] = useState(0)

  const calculated = useMemo(() => {
    console.log('🔁 무거운 계산 실행')
    let sum = 0
    for (let i = 0; i < value * 1000; i++) {
      sum += i
    }
    return sum
  }, [value])

  return (
    <div>
      <p>계산 결과: {calculated}</p>
      <button onClick={() => setValue(value + 1000)}>value 증가</button>
      <button onClick={() => setCount(count + 1)}>count 증가: {count}</button>
    </div>
  )
}

export default ExpensiveCalculation

간단한 설명: 무거운 계산 or 함수 재생성 방지용

결국 함수가 재생성 되는 것을 방지하면서 (캐싱) 성능을 최적화한다고 생각하면 된다.
 
(렌더링을 한 번만 하면 되는데 계속 실행될 때마다 렌더링되면 성능이 하락되는 원리)
 

 
커스텀 Hook 구조 맛보기 (useInput)

일단 코드부터 보고 설명을 진행하겠다.

// useInput.js
import { useState } from 'react'

function useInput(initialValue) {
  const [value, setValue] = useState(initialValue)

  const onChange = (e) => setValue(e.target.value)

  return { value, onChange }
}

export default useInput

// FormComponent.js
import React from 'react'
import useInput from './useInput'

function FormComponent() {
  const name = useInput('')
  const email = useInput('')

  return (
    <div>
      <input placeholder="이름" {...name} />
      <input placeholder="이메일" {...email} />
      <p>이름: {name.value}</p>
      <p>이메일: {email.value}</p>
    </div>
  )
}

export default FormComponent

상단의 코드는 일단 useInput이라는 함수(컴포넌트)를 만들고 밑의 FormComponent라는 컴포넌트에 재사용한 코드이다.

이는 입력값의 상태 관리 로직을 공통적인 훅으로 만들어서 추출한 것이다.

이를 통해 얻을 수 있는 이점은 "특정한 행위"를 재사용함으로 코드의 재사용성 밑 로직을 분리할 수 있다는 것이다.


토론 주제
커스텀 훅을 만드는 기준은 뭘까? (공통 로직 분리 기준)

 

내가 맡은 토론 주제는 "커스텀 훅을 만드는 기준이 무엇인가?"이다.

일단 커스텀 훅을 만드는 방법부터 설명하겠다.

커스텀 hook이란?

Hook을 조합해서 새로운 기능을 만드는 "재사용 가능한 로직 단위" 

  • 이름은 항상 use로 시작해야 함 (useAuth, useInput, useDebounce 등)
  • useState, useEffect, useRef 등을 내부에서 자유롭게 사용 가능

중요한 건 use로 이름을 시작해야 한다는 점이다.

(이름을 짓는 것에 규칙은 따로 없지만, 그래도 해당되는 로직을 이름에 녹이는 것이 좋다.)

예시 상황

모든 상황에서 통하는 것은 아니지만, 그래도 예시를 들자면 다음과 같을 것이다.

 

  • 여러 입력 필드 상태를 관리해야 할 때 → useInput
  • 로그인 상태, 토큰 유지 → useAuth
  • 창 크기 감지, 스크롤 감지 → useWindowSize, useScroll

등등.. 이 밖에도 여러 상황이 있을 것이다.

만드는 기준

커스텀 hook을 만드는 기준은 다음과 같다.

📌 언제 쓰나?

  • 동일한 입력값 관리 로직이 여러 곳에서 반복될 때
  • 로그인 상태 처리, 로컬스토리지와의 연동, API 요청 처리
  • 여러 컴포넌트에서 공통적으로 사용하는 로직이 생겼을 때

결국 "여러 컴포넌트에서 '공통적'으로 사용하는 로직"을 재사용 및 잘 관리하기 위해서 커스텀 hook을 만든다고 결론을 내릴 수 있다.


+ 위의 useInput 커스텀 훅에 대한 영상을 밑에 추가로 세팅해 둠. 참고하길 바람)

https://www.youtube.com/watch?v=S6POUU2-tr8&list=PLZ5oZ2KmQEYjwhSxjB_74PoU6pmFzgVMO&index=10