2026. 1. 1. 23:14ㆍSTUDY
React Hooks란?
함수형 컴포넌트에서 상태(state)와 생명주기(lifecycle) 기능을 사용할 수 있게 해주는 API
→ 클래스 컴포넌트의 복잡함을 줄이고,로직 재상용성을 높임
✓ 클래스컴포넌트
리액트 초기 방식이며, class문법을 사용해다. render()메서지가 필요했으며 this키워드를 사용해 상태나 함수에 접근
로직을 재사용하기 어렵고, this 바인딩 문제로 실수가 잦았다고 한다.
→ class는 내부적으로 인스턴스를 생성하고, this 바인딩을 관리하며, 수많은 생명주기 메서드들을 메모리상에 유지
즉, 컴포넌트 하나가 생성될 때마다 무거운 객체 인스턴스가 메모리에 할당
✓ 함수형컴포넌트
현재 리액트의 표준이며, 함수를 작성하듯 컴포넌트를 만든다.
코드가 훨씬 짧고, 가독성이 높으며 메모리 자원을 덜 사용하여 빌드 결과물의 크기가 작음
→ 호출될 때만 실행되고, 처리가 끝나면 가비지 컬렉션(GC)의 대상이 되기 쉽다. 함수가 실행되는 동안 선언된 변수들은 실행이 끝나면 (클로저에 의해 보존되지 않는 한) 메모리에서 사라진다.
Q 함수형은 인스턴스도 없는데, 어떻게 이전값을 기억할수있는가?
A (리액트 훅) 상태의 위치는 함수 내부가 아니라 리액트 엔진(Fiber라는 내부객체)의 별도 공간에 상태를 보관 → 함수가 실행될 때 리액트가 '저장해둔 상태여기있어' 라는 식으로 빌려주는 방식이다. 함수 자체는 값을 들고 있지 않은 순수한 통로 역할
✓ 상태
컴포넌트 내부에서 변할 수 있는 데이터
상태가 변하면 리액트는 화면을 다시 그린다 (Re-render)
const [count, setCount] = useState(0);
⚠️ 화면을 다시 그린다고 했는데,
화면(실제DOM)은 바뀐 부분만 업데이트되지만, 함수형 컴포넌트 자체는 다시 호출된다.
상태 변경 시, 해당 컴포넌트 함수 내용을 모두 다 읽는다. 만약 내부에 복잡한 계산로직이나 매번 새로 생성되는 객체가 있다면 화면은 안바껴도 CPU는 계속 일하고 있음 -- useMemo, useCallback, React.memo
이미지같은 리소스는 브라우저가 캐싱을 해서 서버에서 받아오는게 아닌 메모리에 있는 이미지를 그대로 보여줌
✓ 생명주기
컴포넌트가 브라우저 화면에 나타나고 사라지는 과정
Mount 컴포넌트가 화면에 나타남 useEffect(() => { ... }, [])
Update 데이터가 변해 화면이 다시 그려짐 useEffect(() => { ... }, [data])
Unmount 컴포넌트가 화면에서 사라짐 return () => { ... } (Clean-up)
Hooks 사용 규칙 (중요)
최상위에서만 호출이 가능
React 함수 컴포넌트 또는 Custom Hook에서만 사용 가능
✓ Custom Hook
리액트 내장 Hook을 조합하여 만든 나만의 hook
여러 컴포넌트에서 비즈니스 로직을 공유할수 있게 해준다.
이름은 use로 시작해야함.
useState
컴포넌트의 상태관리
사례: 입력값 관리, 토글 UI, API 결과 저장
📌 리렌더링 조건을 최소화
const [state, setState] = useState(initialValue)
useEffect
컴포넌트 생명주기 처리 (side effect)
사례: 데이터 fetch, 이벤트 등록/해제, 타이머설정
📌 반드시 cleanup을 챙길것
useEffect(() => {
// 실행 로직
return () => {
// cleanup
}
}, [dependency])
- 실행시점
dependency
없음 -- 매 렌더링마다
[] -- 최초 1회
[state] -- state가 변경될때마다
✓side effect
프로그래밍에선 함수의 본업 외에 발생하는 모든 외부 효과를 뜻한다.
컴포넌트의 본업은 데이터를 받아서 UI로 그려내는 것인데, 그 과정 외에 컴포넌트 외부의 상태를 변경하거나 외부와 상호작용하는 모든 일을 side effect라고 부른다.
대표적으로 API호출(서버에서 데이터를 가져오는 행위, 데이터베이스 상태 의존), DOM직접조작 (document.title 을 바꾸거나 외부 라이브러리를 연결) 등등
왜 uesEffect를 side Effect라고 하는 이유는 리액트의 함수 컴포넌트는 순수 함수 처럼 동작하는 것이 이상적이다. (같은 값을 넣으면 항상 같은 UI가나와야한다.) 하지만 Side Effect는 예측이 어렵고, 렌더링 과정중에 일어나면 버그가 생길수 있다.
그래서 리액트는 렌더링이 다 끝난 후에 화면과 상관 없는 외부의 일(Side Effect)은 여기에 따로 처리하는 의미로 useEffect라는 공간을 만든것이다.
정리되지 않은 Side Effect는 메모리 누수나 예상치 못한 버그의 원인
useEffect(() => {
// [Side Effect 실행]
const timer = setInterval(() => {
console.log("1초마다 실행!");
}, 1000);
// [Cleanup 함수: Side Effect 정리]
// 컴포넌트가 사라지거나(Unmount), 다음 효과가 실행되기 전에 호출됨
return () => {
clearInterval(timer);
console.log("청소 완료!");
};
}, []);
useRef
DOM접근
렌더링과 무관한 값 저장
특징: 값이 변경되도 리렌더링 발생 안함.
사례(사용예시): 렌더링횟수기록(디버깅). 포커스제어(페이지가 열리자 마자 커서위치), 크기측증 등등
📌 불필요한 렌더링을 막는 도구
//DOM 접근 예시
const inputRef = useRef<HTMLInputElement>(null)
<input ref={inputRef} />
<button onClick={() => inputRef.current?.focus()}>
Focus
</button>
// 값 저장 예시
const renderCount = useRef(0)
renderCount.current += 1
✓ 값이 변경되도 리렌더링 발생 안함
컴포넌트는 상태나 전달받은값(props)가 변하면 함수가 다시 실행되는데, useRef에 담긴 값을 바꿀때는 리액트가 감지하지 않음.
즉 실행 → 리액트: "무응답" → 화면은 이전 상태 그대로 유지
useMemo
비용이 큰 계산 결과를 메모이제이션
사례: 반복 연산이 많은 경우, 렌더링 성능 최적화 필요시
[오히려 성능이 느려질수 있다.]
useMemo 자체도 메모리와 비교 비용이 들기 때문에, 연산 비용이 크지 않다면 오히려 오버헤드
성능 문제가 실제로 발생하는 지점에서만 선택적으로 사용해야 합니다. >> 연산 비용이 크거나, 리스트 렌더링 등으로 렌더링 횟수가 많을 때 사용
const total = useMemo(() => {
return items.reduce((sum, item) => sum + item.price, 0)
}, [items])
//useMemo의 두 번째 인자인 의존성 배열([items])에 들어있는 items 값이 이전 렌더링 때와 비교해서 참조값이 달라졌을 때만 내부의 연산(reduce 함수)을 실행
✓ 메모이제이션
한 번 계산한 결과값을 메모리에 저장해 두었다가, 똑같은 계산이 필요할 때 다시 계산하지 않고 저장된 값을 꺼내 쓰는 기술
하지만, 값을 저장하는 것 자체가 메모리를 점유하는 일이기 때문에 낭비해선 안된다.
- 계산 로직이 정말 복잡할때
useCallback
함수를 메모이제이션 , 함수 자체를 기억해서 동일한 참조(reference)를 유지
사례: 자식컴포넌트에 함수 props로 전달하거나, 불필요한 리렌더링방지할 경우
자식 컴포넌트가 React.memo로 감싸져 있고, 함수 props를 받을 때는 참조가 매번 바뀌어 불필요한 리렌더링이 발생할 수 있습니다.
[React.memo만 쓰면 useCallback은 필요 없는가? >> React.memo는 props 비교를 기준으로 동작하는데, 함수는 렌더링마다 새로 생성되기 때문에 useCallback 없이 전달하면 props가 변경된 것으로 인식
const handleClick = useCallback(() => {
console.log(count)
}, [count])
useContext
전역 상태를 props drilling없이 공유
사례: 테마, 로그인 유저정보, 다국어 처리
Context 값 변경 → 하위 컴포넌트 전부 리렌더링
전역 상태 관리 라이브러리(Zustand 등)로 분리 고려
const ThemeContext = createContext("light")
function Child() {
const theme = useContext(ThemeContext)
return <div>{theme}</div>
}
✓ Props Drilling(프롭스 드릴링)
데이터를 전달하기 위해 중간 컴포넌트들이 단순히 도구 역할을 하며 데이터를 아래로 흘러보내는 현상
가옥성이 저하되고, 유지보수가 어여워진다.
useMemo vs useCallback vs React.memo 차이
| 구분 | 메모이제이션 대상 | 사용 위치 |
| useMemo | 값 | 컴포넌트 내부 |
| useCallback | 함수 | 컴포넌트 내부 |
| React.memo | 컴포넌트 | 컴포넌트 선언부 |
보통 이 세가지를 묶어서 사용한다고 한다.
하지만, 메모이제이션도 결국 메모리를 쓰고 비교 연산을 하는 '비용'이 들기 때문에 상황을 보고 사용하자.
- 컴포넌트가 무거울 때: 렌더링 비용이 많이 드는 무거운 컴포넌트에는 적극 활용합니다.
- Props가 자주 변하지 않을 때: 거의 고정적인 UI 요소라면 효과가 큽니다.
- props가 매번 바뀌는 컴포넌트에 React.memo를 쓰면 비교 연산만 추가되고 리렌더링도 발생하기에 성능에 오히려 독
✓ React.memo
훅은 아니고, 고차 컴포넌트 (HOC, Higher Order Component)
보통 부모 컴포넌트라 리렌더링되면, 자식컴포넌트는 props가 변하지 않아도 무조건 리렌더링된다.
React.memo로 자식 컴포넌트를 감짜면, props를 비교하고 변화가 있을때만 리렌더링을 수행
const ChildComponent = React.memo(({ name }) => {
console.log("자식 리렌더링!");
return <div>안녕하세요, {name}님!</div>;
});
function ParentComponent() {
const [count, setCount] = useState(0);
return (
<>
<button onClick={() => setCount(count + 1)}>숫자 올리기</button>
{/* 부모의 count가 변해도 ChildComponent는 name이 그대로면 리렌더링되지 않음 */}
<ChildComponent name="시니어준비생" />
</>
);
}

https://ko.legacy.reactjs.org/docs/hooks-intro.html
Hook의 개요 – React
A JavaScript library for building user interfaces
ko.legacy.reactjs.org
'STUDY' 카테고리의 다른 글
| Next.js(App Router) URL의 파라미터(id)를 가져오는 방식 (서버 컴포넌트와 클라이언트 컴포넌트) (0) | 2026.01.12 |
|---|---|
| SEO를 위한 시맨틱 마크업 (0) | 2026.01.10 |
| Zustand와 Next.js, 안전한 데이터 동기화(Hydration) (0) | 2026.01.04 |
| React Router DOM부터 App Router까지 (0) | 2026.01.03 |
| 상태 관리 라이브러리 -- Zustand (0) | 2026.01.02 |