React Hook의 핵심 개념
React 16.8 이후 도입된 Hook은 클래스 컴포넌트 없이도 상태와 생명주기 처리가 가능한 기능을 제공합니다. 함수형 컴포넌트에서도 상태 관리, 사이드 이펙트 처리, 참조 접근 등 다양한 기능을 활용할 수 있게 되었습니다.
useState: 상태 관리
useState는 함수 컴포넌트 내에서 상태를 선언하고 조작할 수 있도록 해주는 기본 훅입니다. 초기값을 인자로 전달하면, 상태 값과 업데이트 함수를 배열 구조 분해 할당으로 가져올 수 있습니다.
import React, { useState } from 'react';
function Counter() {
const [clickCount, setClickCount] = useState(0);
return (
<div>
<p>클릭 횟수: {clickCount}</p>
<button onClick={() => setClickCount(clickCount + 1)}>증가</button>
</div>
);
}
업데이트 함수는 두 가지 방식으로 사용 가능합니다:
setClickCount(newValue): 새로운 값을 직접 설정setClickCount(prev => prev + 1): 이전 상태를 받아 새 값을 반환하는 함수 형태
useEffect: 부작용 처리
useEffect는 렌더링 후 특정 작업을 수행하도록 설계되었습니다. 클래스 컴포넌트의 componentDidMount, componentDidUpdate, componentWillUnmount의 기능을 하나로 통합한 것입니다.
import React, { useState, useEffect } from 'react';
function Timer() {
const [seconds, setSeconds] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setSeconds(seconds => seconds + 1);
}, 1000);
// 컴포넌트 언마운트 시 정리
return () => clearInterval(interval);
}, []);
return <p>경과 시간: {seconds}초</p>;
}
두 번째 인자인 의존성 배열을 통해 실행 조건을 제어할 수 있습니다. 빈 배열 []은 처음 렌더링 후 한 번만 실행됩니다.
useRef: DOM 참조 및 데이터 저장
useRef는 컴포넌트 내부에서 특정 요소나 임의의 값을 지속적으로 유지할 수 있도록 합니다. 주로 입력 필드의 값 접근이나 타이머 제어에 사용됩니다.
import React, { useRef } from 'react';
function InputFocus() {
const inputRef = useRef();
const handleFocus = () => {
inputRef.current.focus();
};
return (
<div>
<input ref={inputRef} type="text" />
<button onClick={handleFocus}>포커스 설정</button>
</div>
);
}
useContext: 상위 컨텍스트 값 공유
다중 레벨의 컴포넌트 간 데이터 전달을 위한 방식으로, createContext로 생성한 컨텍스트를 useContext로 소비할 수 있습니다.
import React, { createContext, useContext, useState } from 'react';
const ThemeContext = createContext('light');
function App() {
const [theme, setTheme] = useState('dark');
return (
<ThemeContext.Provider value={theme}>
<Header />
<MainContent />
<button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>테마 변경</button>
</ThemeContext.Provider>
);
}
function Header() {
const theme = useContext(ThemeContext);
return <h1 style={{ color: theme === 'dark' ? 'white' : 'black' }}>헤더 (현재 테마: {theme})</h1>;
}
useMemo: 계산 결과 캐싱
복잡한 계산을 포함하는 값은 의존성 변화 시에만 재계산되도록 하여 성능을 최적화합니다.
import React, { useMemo, useState } from 'react';
function ExpensiveComponent({ items }) {
const expensiveValue = useMemo(() => {
return items.reduce((sum, item) => sum + item.price, 0);
}, [items]);
return <p>총 가격: {expensiveValue}</p>;
}
useCallback: 함수 참조 재사용
함수를 자식 컴포넌트에 전달할 때, 의존성 변경 시에만 새로운 함수를 생성하여 불필요한 리렌더링을 방지합니다.
import React, { useCallback, useState } from 'react';
function Child({ onClick }) {
return <button onClick={onClick}>클릭하기</button>;
}
function Parent() {
const [count, setCount] = useState(0);
const handleClick = useCallback(() => {
setCount(count + 1);
}, [count]);
return <Child onClick={handleClick} />;
}
useTransition: 비동기 렌더링 우선순위 조절
useTransition는 대량 데이터 로딩이나 무거운 계산을 수행할 때, 사용자 인터페이스 반응성을 유지하기 위해 렌더링 우선순위를 낮추는 데 사용됩니다.
import React, { useState, useTransition } from 'react';
function SearchList() {
const [query, setQuery] = useState('');
const [list, setList] = useState([]);
const [isPending, startTransition] = useTransition();
const handleChange = (e) => {
const value = e.target.value;
setQuery(value);
startTransition(() => {
const largeList = Array.from(new Array(10000), (_, i) => i + Date.now());
setList(largeList);
});
};
return (
<div>
<input value={query} onChange={handleChange} />
{isPending ? <span>로딩 중...</span> : <ul>{list.map(item => <li key={item}>{item}</li>)}</ul>}
</div>
);
}
useReducer: 복잡한 상태 로직 관리
상태가 여러 개이고 상호 의존적인 경우, useReducer를 통해 액션 기반의 상태 업데이트를 구현할 수 있습니다.
import React, { useReducer } from 'react';
const initialState = { count: 0, name: '홍길동' };
function reducer(state, action) {
switch (action.type) {
case 'INCREMENT':
return { ...state, count: state.count + 1 };
case 'SET_NAME':
return { ...state, name: action.payload };
default:
throw new Error('Unknown action');
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div>
<p>이름: {state.name}, 카운트: {state.count}</p>
<button onClick={() => dispatch({ type: 'INCREMENT' })}>+1</button>
<input
value={state.name}
onChange={(e) => dispatch({ type: 'SET_NAME', payload: e.target.value })}
/>
</div>
);
}
React.memo: Props 기반 메모이제이션
props가 변경되지 않으면 리렌더링을 생략하는 고차원 컴포넌트입니다. 단, 내부 상태나 컨텍스트 변화는 영향을 받습니다.
import React from 'react';
const ExpensiveComponent = React.memo(({ data }) => {
console.log('렌더링됨');
return <div>{data.map(d => d.value).join(', ')}</div>;
});