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 변수
이에 대한 비교를 아래 게시글에 정리했다.
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 는 초기화된다.
상세한 내용은 아래 게시글에 정리했다.
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) 리뷰로 작성 되었습니다.
'학습일지 > K-Digital Traing' 카테고리의 다른 글
| [KDT] AIaaS 마스터클래스 8주차 - 리액트 상태관리와 Redux (1) | 2025.05.16 |
|---|---|
| [KDT] AIaaS 마스터클래스 8주차 - 이벤트 핸들링과 상태(state) (0) | 2025.05.15 |
| [KDT] AIaaS 마스터클래스 8주차 - React 학습 (0) | 2025.05.12 |
| [KDT] AIaaS 마스터클래스 7주차 - NC Dinos 홈페이지의 버그 고쳐보기 (0) | 2025.05.07 |
| [KDT] AIaaS 마스터클래스 6주차 - 테라폼, 깃허브 액션 실습 (5) | 2025.04.28 |