학습일지/K-Digital Traing

[KDT] AIaaS 마스터클래스 8주차 - 리액트 Hook

tierr 2025. 5. 14. 17:51

1. 리액트 Hook

Hook은 React 함수형 컴포넌트에서 상태(state), 생명주기(lifecycle), context 등 React 기능을 사용할 수 있게 해주는 함수
  • React 16.8부터 도입
  • 클래스 없이도 강력한 기능을 함수에서 사용 가능
  • 항상 use로 시작 (useState, useEffect, useRef 등)

🧠 왜 필요한지? 장점이 무엇인지?

기존에는 다음과 같은 제약이 있었어요:

함수형 컴포넌트 클래스 컴포넌트
단순한 렌더링만 가능 상태 관리, 라이프사이클 사용 가능

👉 Hook이 생기면서 함수형에서도 클래스 수준 기능을 사용 가능하게 됨!

  • 간결한 코드: 클래스형 컴포넌트보다 간결하고 이해하기 쉬운 코드를 작성가능
  • 상태 관리 간편화: 함수형 컴포넌트 내에서도 쉽게 상태관리 가능
  • 코드 재사용성 향상: 커스텀 Hook을 통해 재사용

🔧 대표적인 내장 Hook 목록

Hook 이름 역할
useState 상태(state) 관리
useEffect 컴포넌트가 마운트, 업데이트, 언마운트될 때 수행할 작업 등록
useRef DOM 요소 또는 변경되어도 리렌더링하지 않아야 할 값 참조
useContext Context API 값 사용
useReducer 복잡한 상태 관리 (Redux와 유사한 방식)
useMemo 계산 결과를 메모이제이션 (성능 최적화)
useCallback 함수 자체를 메모이제이션 (불필요한 재생성 방지)
useLayoutEffect DOM 변경 직후 동기적으로 실행 (UI 깜빡임 방지용)
useImperativeHandle ref를 사용할 때 자식 컴포넌트에서 공개할 메서드 지정 (고급)

1-1. 각각의 내장 Hook 에 대하여

✅ 1. useState

상태(state)를 저장하고 업데이트할 수 있게 해주는 Hook

📌 비유: "리액트 컴포넌트 안에 기억장치 하나 만든다"

const [count, setCount] = useState(0);
  • count는 값 (기억장치 안에 들어있는 값)
  • setCount(1)처럼 호출하면 컴포넌트가 다시 렌더링되며 값 반영됨

🧪 예: 버튼 클릭해서 숫자 늘리기


✅ 2. useEffect

컴포넌트가 처음 렌더링되거나 값이 바뀔 때 실행되는 코드 (side effect)

📌 비유: "리액트가 화면을 그리고 난 다음 자동으로 실행되는 감시 로봇"

useEffect(() => {
  console.log("컴포넌트가 나타남!");

  return () => {
    console.log("컴포넌트가 사라짐!");
  };
}, []);
  • 의존성 배열이 [] → 컴포넌트가 처음 나타날 때 1회만 실행
  • 값이 들어가면 → 그 값이 바뀔 때마다 실행됨

🧪 예: API 호출, 이벤트 등록/해제, 타이머


✅ 3. useRef

DOM 요소를 참조하거나, 렌더링 없이 값을 기억하고 싶을 때 사용

📌  비유: "렌더링에 영향을 주지 않는 숨겨진 메모장"

const inputRef = useRef(null);
 
  • DOM 접근: inputRef.current.focus()
  • 값 저장소: ref.current = 값 ← 바꿔도 리렌더링 X

🧪 예: input에 자동 포커스, 이전 값 추적, 타이머 ID 저장


✅ 4. useContext

부모에서 만든 Context 값을 하위 컴포넌트에서 쉽게 받아쓸 수 있게 해줌

📌 비유: "전역으로 공유되는 데이터 통로를 꽂아 쓰는 느낌"

const user = useContext(UserContext);
  • UserContext.Provider에서 제공한 값을 꺼내 쓸 수 있음

🧪 예: 사용자 정보, 테마, 언어 설정


✅ 5. useReducer

복잡한 상태 관리 (값이 여러 개거나 상태 전이가 복잡할 때)

📌 비유: "상태 업데이트를 스위치처럼 분기 처리함 (Redux 느낌)"

const [state, dispatch] = useReducer(reducer, initialState); 

🧪 예: 로그인 상태, 폼 유효성 검사, 여러 상태 묶음


✅ 6. useMemo

계산이 무거운 작업의 결과를 기억(memoization)해서 성능을 높임

📌 비유: "값 계산이 오래 걸릴 땐, 같은 값이면 다시 계산하지 마!"

const result = useMemo(() => {
  return heavyComputation(data);
}, [data]);

🧪 예: 필터링/정렬된 목록, 차트 계산 등

 

DeepDive) useMemo()는 "직전에 계산했던 값"만 기억하는가? 즉, 캐시가 하나뿐인가?

그렇다. useMemo는 오직 "가장 최근 계산값 한 개만" 기억한다.

 

💡 언제 유용할까?

  • 렌더링마다 반복 계산이 비용이 큰 경우 (예: 정렬, 필터링, 합계 계산 등)
  • 컴포넌트가 자주 리렌더링되지만, 계산할 값은 자주 안 바뀔 때

✅ 7. useCallback

함수를 메모이제이션해서 불필요한 재생성을 막음

📌 비유: "자식 컴포넌트에 props로 함수를 줄 땐, 매번 새로 만들지 말고 기억해서 써"

const handleClick = useCallback(() => {
  console.log("클릭됨!");
}, []); 

🧪 예: React.memo()와 함께 사용하면 성능 최적화


✅ 8. useLayoutEffect

useEffect처럼 생겼지만, DOM이 그려지기 전에 동기적으로 실행

📌 비유: "그림 그리기 전에 고치고 싶은 게 있을 때 쓰는 고속 작업용 로봇"

useLayoutEffect(() => {
  // DOM 변경 전 빠르게 실행됨
}, []);

🧪 예: 레이아웃 계산, 스크롤 위치 초기화


✅ 9. useImperativeHandle

ref를 통해 자식 컴포넌트가 외부에 제한된 함수만 제공하게 만듦 (고급 기능, 거의 라이브러리 개발용)
useImperativeHandle(ref, () => ({
  focus: () => inputRef.current.focus()
})); 

🧪 예: 외부에서 .focus() 같은 특정 메서드만 접근 허용


🎯 마무리 요약표

Hook 주 역할 한줄 요약
useState 상태 저장 화면에 반영되는 값 관리
useEffect 부수 효과 API 호출, 타이머, 감시 작업
useRef 값 보존, DOM 접근 렌더링 없이 값 기억 또는 DOM 직접 제어
useContext 전역 상태 사용 상위에서 전달된 Context 값 읽기
useReducer 복잡한 상태 처리 Redux 스타일 분기 처리
useMemo 계산 캐싱 성능 최적화용 값 캐싱
useCallback 함수 캐싱 props로 넘길 함수의 참조 유지
useLayoutEffect 렌더 전 실행 DOM 계산/수정 동기 처리 ※자주 사용 X
useImperativeHandle 외부에 ref 기능 제한 노출 고급 ref 제어 ※자주 사용 X

DeepDive) useState vs useRef vs 변수

이에 대한 비교를 아래 게시글에 정리했다.

https://tierr.tistory.com/94

 

React) useState vs useRef vs 변수

리액트 Hook에 대해 공부하던 중, useRef 와 일반 변수의 차이점에 대해 궁금해졌고,겸사겸사 useState도 함께 비교해보기 위해 본 게시글을 작성했다.1. 핵심 비교표: useState vs useRef vs 일반 변수(let, co

tierr.tistory.com



2. Context API

Context API는 리액트 컴포넌트 트리 전체에 데이터를 전역으로 공유할 수 있게 해주는 기능입니다.

  • props 없이도 데이터 전달 가능
  • 상위 → 하위 컴포넌트로 데이터를 전달할 때 유용
  • Redux 같은 외부 라이브러리 없이 전역 상태 관리 가능

🧠 왜 필요할까?

React는 원래 데이터를 부모 → 자식 → 자식... 으로 props로 넘겨야 했어요.

<App user={user}>
  <Header user={user}>
    <Nav user={user}>
      <UserInfo user={user} />
    </Nav>
  </Header>
</App>
 
 

이걸 "prop drilling" (프롭 뚫기) 라고 부르는데, 너무 깊어지면 유지보수가 어렵고 비효율적입니다.

👉 그래서 Context를 쓰면 어디서든 바로 꺼내 쓸 수 있어요!


2-1. Context 구성 순서 (3단계)

1️⃣ Context 생성

const UserContext = React.createContext();

 

2️⃣ Provider로 값 전달

<UserContext.Provider value={{ name: "짱구" }}>
  <App />
</UserContext.Provider>
  • Provider는 "우산"처럼 감싸는 것
  • 하위 컴포넌트들이 value를 받아 씀

3️⃣ 하위에서 값 사용

const user = useContext(UserContext);
console.log(user.name); // "짱구"

 

useContext()로 상위 Provider에서 준 값에 접근

 

✅ 실전 예제

더보기
더보기
// context/UserContext.js
import { createContext } from "react";
export const UserContext = createContext(null);
// App.js
import { UserContext } from "./context/UserContext";
import { Profile } from "./components/Profile";

export default function App() {
  const user = { name: "짱구", age: 7 };

  return (
    <UserContext.Provider value={user}>
      <Profile />
    </UserContext.Provider>
  );
}
// components/Profile.js
import { useContext } from "react";
import { UserContext } from "../context/UserContext";

export function Profile() {
  const user = useContext(UserContext);
  return (
  <>
    <p>이름: {user.name}</p>
    <p>나이: {user.age}</p>
  </>
  );
}

 

DeepDive) 그렇다면 UserContext 는 언제 초기화되는가?

여기서 value={user}를 넘기는 순간 UserContext 는 초기화된다.

상세한 내용은 아래 게시글에 정리했다.

https://tierr.tistory.com/95

 

React) Context 는 언제 초기화될까

context API 에 대해 아래 예제 코드를 통해 공부하고 있었다. Context를 전역변수처럼 편리하게 사용할 수 있다는 맥락은 알았으나 그렇다면 Context의 초기화는 언제 이루어지는지에 대한 궁금증이

tierr.tistory.com


2-2. 마무리

🎯 언제 쓰면 좋은가?

상황 Context 추천 여부
테마(다크모드), 언어(locale) 설정 ✅ 추천
로그인한 사용자 정보(user) ✅ 추천
단순하게 한두 단계만 내려보내는 값 ❌ 그냥 props로도 충분
대규모 상태 관리 필요 (CRUD, 복잡한 전환 등) ❌ Redux, Zustand 등 외부 라이브러리 고려

✅ 요약 정리

항목 설명
역할 컴포넌트 트리 전체에서 전역 값 공유
핵심 구성 createContext, Provider, useContext
장점 props 없이 하위 컴포넌트에서 값 접근
단점 남용하면 추적 어려움 / 성능 저하 가능
대표 사용 예 사용자 정보, 테마, 언어, 로그인 상태 등

2-3. 번외 : Context.displayName 에 대하여

Context.displayName은 React Developer Tools에서 Context 이름을 알아보기 쉽게 보여주기 위해 사용하는 속성.

 

왜 필요한가?

React.createContext()로 만든 Context는 디버깅 도구에서 이름이 Context로만 보일 수 있음

const MyContext = React.createContext();
  

React DevTools에선 이렇게 보일 수 있음:

<Context.Provider> 

👀 이러면 어떤 Context인지 알아보기 어렵다

그래서 쓰는 게 displayName

const UserContext = React.createContext();
UserContext.displayName = "UserContext";

이렇게 하면 React Developer Tools에선:

<UserContext.Provider>​
 처럼 명확하게 표시된다

사용 위치

// userContext.js
import { createContext } from 'react';

export const UserContext = createContext();
UserContext.displayName = "UserContext";

 

✅ 요약

항목 설명
목적 React DevTools에서 Context 이름을 명확히 보이게
언제 쓰나? Context가 많거나 디버깅 중 헷갈릴 때
필수인가? ❌ 아니요. 선택사항이지만 디버깅 시 매우 유용
어디서 설정? createContext() 다음 줄에 .displayName = '이름' 지정

3. Custom Hook

Custom Hook은 여러 컴포넌트에서 공통적으로 사용하는 로직(state, effect 등)을 재사용 가능한 함수로 분리한 것입니다.

 

3-1. Custom Hook의 장점

  • 코드 재사용: 같은 로직을 여러 컴포넌트에서 반복하지 않고, 하나의 훅으로 추출하여 재사용 가능

  • 가독성 향상: 컴포넌트의 로직이 간결해지고, 각각의 훅은 특정 기능에만 집중 가능
  • 테스트 용이성: 커스텀 훅은 함수이므로 독립적으로 테스트 가능

예를 들어...

const [value, setValue] = useState("");
useEffect(() => {
  localStorage.setItem("key", value);
}, [value]);

이걸 여러 컴포넌트에 복붙하면 중복도 많고 유지보수도 불편함

 

👉 그래서 이걸 커스텀 Hook으로 묶으면:

const [value, setValue] = useLocalStorage("key", "");

🎯 사용은 간단하게, 내부 로직은 깔끔하게 캡슐화할 수 있다.


3-2. Custom Hook을 만드는 방법

✅ 커스텀 훅은 단순히 use로 시작하는 함수라고 생각하면 쉽다.

// useInput.js
import { useState } from "react";

export default function useInput(initValue) {
  const [value, setValue] = useState(initValue);
  const onChange = (e) => setValue(e.target.value);
  return [value, onChange];
}

 

✅ 사용

const [text, handleChange] = useInput("초기값");

 

✅ 예제 : 대중적으로 많이 사용되는 custom Hook인 useFetch 를 만들어보자.

데이터는 가짜 Rest API 서버인 {JSON} Placeholder (https://jsonplaceholder.typicode.com/) 를 통해 받아오도록 한다.

 

* {JSON} Placeholder 이용 방법에 대해서는 아래 게시글에 정리해뒀으니 참고

더보기
더보기
// App.js
import React from "react";
import useFetch from "./components/useFetch";

function App() {
  const { data, loading, error } = useFetch(
    "https://jsonplaceholder.typicode.com/posts"
  );

  if (loading) return <p>로딩 중...</p>;
  if (error) return <p>에러 발생: {error}</p>;

  return (
    <div>
      <h1>Posts</h1>
      <ul>
        {data.map((post) => (
          <li key={post.id}>
            <h3>{post.title}</h3>
            <p>{post.body}</p>
          </li>
        ))}
      </ul>
    </div>
  );
}

export default App;

 

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

function useFetch(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    const fetchData = async () => {
      try {
        const response = await fetch(url);
        if (!response.ok) {
          throw new Error("데이터를 가져오는데 실패했습니다");
        }
        const result = await response.json();
        setData(result);
      } catch (error) {
        setError(error.message);
      } finally {
        setLoading(false);
      }
    };

    fetchData();
  }, [url]);

  return { data, loading, error };
}

export default useFetch;

3-3.  Custom Hook 을 이용하기 위한 조건

조건 설명
이름이 use로 시작해야 함 그래야 React가 "이건 Hook이다"라고 인식함
다른 Hook(useState, useEffect, etc.)을 내부에서 사용할 수 있음 일반 함수는 못 함
컴포넌트 최상단에서 호출해야 함 Hook의 규칙 그대로 따름
 

📦 예시: 자주 쓰이는 Custom Hook 이름

이름 역할
useInput input 상태 + onChange 묶기
useFetch API 요청 및 로딩/에러 상태 관리
useToggle 불리언 상태 토글 (true/false 전환)
usePrevious 이전 값 추적
useDebounce 입력 지연 처리 (디바운싱)

3-4. 마무리

항목 설명
정의 Hook(useState, useEffect, etc.) 로직을 함수로 묶은 것
목적 코드 재사용, 깔끔한 구성
규칙 이름은 use로 시작, 다른 Hook 사용 가능
쓰는 이유 중복 제거, 유지보수 용이, 가독성 향상

 

 

DeepDive)  Custom Hook과 일반 함수는 뭐가 다른가? 그냥 함수 쓰면 안되나?

Custom Hook은 React의 상태 관리(Hook 시스템)에 참여할 수 있는 함수이고, 일반 함수는 React의 상태 시스템과 아무 관련이 없다. 즉, React의 상태 시스템에 접근하기 위해서는 Hook을 사용해야 하기 때문에 차이가 있다!


수업) React 주요 개념 조사하고 학습일지 정리하기

1. Memoization 의 개념과 React 에서의 활용 방식 정리하기

 

Memoization이란?

Memoization은 "렌더링할 필요 없는 걸 하지 않게 만들어주는 것"
이미 계산한 값을 저장해서, 같은 입력이 들어오면 다시 계산하지 않고 저장된 값을 재사용하는 기법
더보기
더보기

📌 예를 들어:

function add(a, b) {
  return a + b;
}

// memoization 적용 버전
const cache = {};
function memoizedAdd(a, b) {
  const key = `${a},${b}`;
  if (cache[key]) return cache[key];
  const result = a + b;
  cache[key] = result;
  return result;
}

🔁 같은 입력이 들어오면 한 번만 계산하고, 그 뒤엔 캐시된 값 사용!

✅ React에서의 Memoization 활용

React는 렌더링이 자주 발생하므로, "변경되지 않은 것까지 재계산/재렌더링 되는 걸 막는 것"이 중요하다.
그래서 다음과 같은 방식들을 활용해 memoization을 적용한다.
- React. memo
- useMemo
- useCallback

 

더보기) 위 방식들의 상세 예제

더보기
더보기

1. React.memo (컴포넌트 메모이제이션)

props가 바뀌지 않으면 리렌더링 하지 않도록 함

 

const MyComponent = React.memo(function MyComponent({ name }) {
  console.log('렌더링됨!');
  return <div>안녕, {name}</div>;
});
  • React.memo는 props가 같으면 렌더링 건너뜀
  • 주로 Pure Component처럼 쓰임

2. useMemo (계산된 값 메모이제이션)

복잡한 연산 결과를 캐싱해서 성능 최적화

const expensiveValue = useMemo(() => {
  return slowFunction(input); // 비싼 연산
}, [input]); // input이 바뀔 때만 재계산

 

  • 렌더링마다 slowFunction()을 계속 실행하지 않도록 방지
  • 대표적 성능 최적화 패턴

3. useCallback (함수 메모이제이션)

함수 객체가 불필요하게 새로 생성되는 것을 방지

const handleClick = useCallback(() => {
  console.log('클릭!');
}, []); // 의존성이 바뀌지 않으면 같은 함수 재사용

 

  • 자식 컴포넌트에 콜백 함수 props로 전달할 때 유용
  • useCallback 없이 매 렌더마다 새로운 함수가 생기면 불필요한 리렌더링 유발

언제 꼭 써야 할까?

상황 사용 예
컴포넌트가 무거운 연산을 할 때 useMemo
함수 props가 자식 컴포넌트 리렌더링을 유발할 때 useCallback, React.memo
리스트 항목이 많고 리렌더링 비용이 클 때 React.memo + key

요약

도구 설명
React.memo 컴포넌트 자체를 메모이제이션 (props 비교)
useMemo 계산된 값 메모이제이션
useCallback 함수 메모이제이션

2. useReducer 와 context API 의 상태 관리 방식의 차이 이해하기

 

✅ 차이 요약: 목적이 다르다

항목 useReducer Context API
목적 복잡한 상태의 변경 로직 관리 상태를 여러 컴포넌트에 공유
기본 역할 useState 대체 → 상태 변경 구조화 props drilling 방지 → 전역처럼 공유
디자인 철학 상태를 "어떻게 바꿀지" 명확하게 정의 상태를 "어디서든 접근" 가능하게 만듦
 

useReducer 란?

useState보다 상태 변경 방식이 복잡할 때 쓰는 훅
상태 변경 로직을 reducer 함수로 분리하여 관리
더보기
더보기
const reducer = (state, action) => {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    default:
      return state;
  }
};

const [state, dispatch] = useReducer(reducer, { count: 0 });
    • dispatch({ type: 'increment' })처럼 액션 기반 변경
    • 상태 변경 흐름이 명확해지고 테스트가 쉬워짐

 

Context API란?

상위 컴포넌트의 상태를 중첩된 하위 컴포넌트에 props 없이 전달
즉, 상태를 전역처럼 공유할 수 있게 해주는 React의 내장 기능
더보기
더보기
const CountContext = createContext();
const App = () => (
  <CountContext.Provider value={someValue}>
    <MyComponent />
  </CountContext.Provider>
);

const value = useContext(CountContext);
  • useContext()로 어디서든 값 사용 가능
  • 하지만 자체적으로 상태를 변경하는 기능은 없음

 

✅ 함께 쓰는 게 일반적이다

🧩 useReducer + Context 조합은 실제로 전역 상태를 구조적으로 관리하는 방식으로 많이 사용

// context + reducer 조합
const CountContext = createContext();

const reducer = (state, action) => {
  switch (action.type) {
    case 'add':
      return { count: state.count + 1 };
    default:
      return state;
  }
};

function CountProvider({ children }) {
  const [state, dispatch] = useReducer(reducer, { count: 0 });

  return (
    <CountContext.Provider value={{ state, dispatch }}>
      {children}
    </CountContext.Provider>
  );
}

  ✅ 구조적 상태 변경 + 전역 공유 = 상태 관리의 기본 패턴

 

✅ 언제 뭘 쓰면 되나?

상황 추천 방식
상태 변경 로직이 단순하고 컴포넌트 내부용 useState
상태가 복잡하고 조건에 따라 바뀌는 경우 useReducer
전역으로 데이터를 여러 컴포넌트에 전달해야 할 때 Context API
전역 상태 공유 + 구조적 상태 변경이 필요 useReducer + Context 조합
 

✅ 정리 문장

useReducer는 "어떻게 바꿀지" 정의
Context는 "어디서든 접근할 수 있게" 전달
💡 함께 쓰면 전역 상태 관리가 깔끔해진다!


본 후기는 [카카오엔터프라이즈x스나이퍼팩토리] 카카오클라우드로 배우는 AIaaS 마스터 클래스 (B-log) 리뷰로 작성 되었습니다.