본문 바로가기

개발이야기

React 고급 기술 익히기 - 10편: React Hooks의 모든 것

React의 Hooks는 컴포넌트의 상태 및 생명 주기 기능을 클래스 없이도 사용할 수 있도록 만든 기능입니다. React 16.8에서 처음 도입되었으며, 이후 버전에서도 지속적으로 개선되고 있습니다. 특히 React 19에서는 새로운 훅이 추가되면서 더 강력한 기능을 제공하고 있습니다.

이번 글에서는 Hooks의 기본 개념, 등장 배경, 내부 동작 원리, 모든 Hook의 기능과 사용법, 그리고 앞으로의 방향성에 대해 다룹니다. 🚀


1. Hooks의 등장 배경과 동작 원리

1.1 Hooks는 왜 등장했을까?

React에서 클래스 기반 컴포넌트는 다음과 같은 문제점을 가지고 있었습니다.

  • 상태 관리가 복잡: this.state를 계속 관리해야 하며, 상태 변경 로직이 흩어져 있음
  • 재사용성이 낮음: 같은 로직을 여러 컴포넌트에서 사용하려면 HOC(Higher Order Component)나 Render Props 패턴을 사용해야 했음
  • 긴 컴포넌트 생명주기: componentDidMount, componentDidUpdate, componentWillUnmount 등 라이프사이클 메서드가 분산됨

이 문제를 해결하기 위해 React 16.8에서 Hooks가 도입되었습니다. Hooks를 사용하면 함수형 컴포넌트에서도 상태와 생명주기 기능을 쉽게 활용할 수 있습니다.

1.2 Hooks의 동작 원리

Hooks는 React 내부에서 상태를 저장하고 관리하는 방식을 개선한 것입니다. 함수형 컴포넌트는 호출될 때마다 초기화되지만, React는 Hook이 호출된 순서를 기억하고 이전 상태를 유지합니다.

function Counter() {
  const [count, setCount] = React.useState(0);

  return (
    <button onClick={() => setCount(count + 1)}>
      클릭 횟수: {count}
    </button>
  );
}

React는 useState(0)가 첫 번째 호출된 위치를 기억하고, 이후 다시 렌더링될 때 해당 위치의 값을 반환하여 상태를 유지합니다.

1.3 React 내부에서 Hooks가 어떻게 동작하는가?

React 내부에서 Hooks가 동작하는 방식은 ReactFiberHooks.js에 구현되어 있습니다. React는 렌더링 과정에서 훅의 호출 순서를 유지하고, 상태를 fiber.memoizedState에 저장합니다.

function mountState(initialState) {
  const hook = mountWorkInProgressHook();
  hook.memoizedState = initialState;
  return [hook.memoizedState, dispatchAction.bind(null, hook)];
}

function updateState() {
  const hook = updateWorkInProgressHook();
  return [hook.memoizedState, dispatchAction.bind(null, hook)];
}

✅ mountState()는 초기 상태를 설정하고, updateState()는 상태를 갱신하는 역할을 합니다.


2. React에서 제공하는 모든 Hooks (React 19 포함)

React에서 제공하는 모든 Hooks를 카테고리별로 정리하고, 각각의 기능과 사용 예시를 설명합니다.

2.1 상태 관리 Hooks

useState - 상태 관리

  • 컴포넌트의 상태를 선언하고 변경할 수 있도록 해주는 Hook입니다.
  • 값이 변경될 때마다 해당 컴포넌트가 리렌더링됩니다.
  • 상태의 초기 값을 설정할 수 있으며, 이전 상태를 기반으로 업데이트할 수도 있습니다.
const [count, setCount] = useState(0);
setCount(prev => prev + 1); // 이전 값을 기반으로 업데이트

useReducer - 복잡한 상태 관리

  • useState보다 더 복잡한 상태 업데이트 로직이 필요할 때 사용합니다.
  • Redux와 비슷한 개념으로, dispatch를 사용하여 상태를 변경합니다.
  • 상태가 여러 단계로 변경되는 경우 유용하게 활용됩니다.
const reducer = (state, action) => {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    default:
      return state;
  }
};

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

2.2 DOM 관련 Hooks

useRef - DOM 요소 또는 값 참조

  • ref 객체를 생성하여 특정 요소에 접근하거나 값을 저장할 수 있습니다.
  • 값이 변경되어도 렌더링을 발생시키지 않습니다.
  • 주로 포커스 제어나 DOM 요소 조작에 사용됩니다.
const inputRef = useRef(null);
<input ref={inputRef} />;

useImperativeHandle - ref의 동작 제어

  • 부모 컴포넌트가 특정 함수만 접근할 수 있도록 제한할 때 사용됩니다.
  • forwardRef와 함께 사용하여 자식 컴포넌트의 동작을 제어할 수 있습니다.
  • 보안이나 특정 기능을 제한적으로 노출할 때 유용합니다.
const ChildComponent = forwardRef((props, ref) => {
  useImperativeHandle(ref, () => ({
    focus: () => inputRef.current.focus()
  }));
  return <input ref={inputRef} />;
});

2.3 성능 최적화 Hooks

useMemo - 값 메모이제이션

  • 계산 비용이 큰 작업의 결과를 저장하여 불필요한 재계산을 방지합니다.
  • 특정 값이 변경될 때만 다시 계산되도록 설정할 수 있습니다.
  • 렌더링 성능을 최적화하는 데 도움을 줍니다.
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

useCallback - 함수 메모이제이션

  • 함수를 메모이제이션하여 불필요한 함수 재생성을 방지합니다.
  • 함수가 종속성 배열의 값이 변경될 때만 새로 생성됩니다.
  • React.memo와 함께 사용하면 더욱 효과적입니다.
const handleClick = useCallback(() => doSomething(value), [value]);

2.4 비동기 처리 및 서버 Hooks

useTransition - 비동기 상태 관리

  • UI의 응답성을 높이기 위해 상태 업데이트의 우선순위를 조정합니다.
  • 중요도가 낮은 업데이트를 지연하여 성능을 최적화합니다.
const [isPending, startTransition] = useTransition();
startTransition(() => setState(newValue));

useDeferredValue - 지연된 값 처리

  • 값 업데이트를 늦춰서 성능을 최적화하는 Hook입니다.
  • 사용자 입력과 관련된 렌더링 부담을 줄이는 데 유용합니다.
const deferredValue = useDeferredValue(inputValue);

useSuspense - 비동기 데이터 로딩

  • 비동기 데이터를 로딩할 때 Suspense를 사용하여 대체 UI를 제공할 수 있습니다.
<Suspense fallback={<Loading />}>
  <MyComponent />
</Suspense>

useServerProps - 서버 상태 관리 (React 19 신규)

  • 서버에서 데이터를 가져오고 클라이언트에서 최신 상태를 유지하는 기능을 합니다.
const [data, setData] = useServerProps();

useFormState - 폼 상태 관리 (React 19 신규)

  • 폼 입력값을 쉽게 관리하고 서버와 연동하여 폼 상태를 최적화하는 기능을 제공합니다.
const [formState, formAction] = useFormState();

 

 

2.5 React의 생명주기 관련 Hooks

useEffect - 사이드 이펙트 관리

  • 컴포넌트가 마운트, 업데이트, 언마운트될 때 특정 동작을 수행할 수 있습니다.
  • API 호출, 이벤트 리스너 등록/해제 등 부수적인 작업을 처리하는 데 유용합니다.
  • 종속성 배열을 사용하여 특정 값이 변경될 때만 실행되도록 할 수 있습니다.
useEffect(() => {
  console.log("컴포넌트가 마운트되었습니다.");
  return () => console.log("언마운트되었습니다.");
}, []);

useLayoutEffect - 동기적 사이드 이펙트 처리

  • useEffect와 비슷하지만, 렌더링 이후 브라우저가 화면을 그리기 전에 실행됩니다.
  • 레이아웃 측정 및 동기적인 DOM 업데이트가 필요할 때 사용됩니다.
useLayoutEffect(() => {
  console.log("DOM 변경 후 실행됨");
});

2.6 이벤트 및 사용자 입력 처리 Hooks

useId - 고유한 ID 생성 (React 18 추가)

  • 서버와 클라이언트에서 동일한 ID를 유지할 수 있도록 보장합니다.
  • Form 요소 또는 접근성을 위한 aria-labelledby 속성 등에 활용됩니다.
const id = useId();
<label htmlFor={id}>이름</label>
<input id={id} />

useEvent - 최적화된 이벤트 핸들러

  • React 19에서 새롭게 추가된 훅으로, 이벤트 핸들러를 최적화하여 불필요한 렌더링을 방지합니다.
  • 이벤트 핸들러를 useCallback 없이도 메모이제이션하여 성능을 개선합니다.
const handleClick = useEvent(() => {
  console.log("클릭 이벤트 발생");
});

2.7 커스텀 훅(Custom Hooks) 활용

커스텀 훅이란?

  • 여러 컴포넌트에서 재사용 가능한 로직을 정의할 수 있도록 도와줍니다.
  • 기존의 React Hooks를 조합하여 새로운 기능을 제공할 수 있습니다.
function useLocalStorage(key, initialValue) {
  const [storedValue, setStoredValue] = useState(() => {
    const item = localStorage.getItem(key);
    return item ? JSON.parse(item) : initialValue;
  });

  const setValue = value => {
    setStoredValue(value);
    localStorage.setItem(key, JSON.stringify(value));
  };

  return [storedValue, setValue];
}

3. React Hooks의 발전 방향

React Hooks는 지속적으로 발전하고 있으며, 앞으로도 더 많은 기능이 추가될 가능성이 큽니다.

✅ 자동 최적화 강화: React Compiler가 Hooks 내부적으로 최적화하여 useMemo, useCallback을 자동으로 처리할 가능성이 있음
✅ 비동기 처리 개선: useSuspense, useOptimistic 등 비동기 렌더링을 최적화하는 새로운 Hooks 등장
✅ 서버 컴포넌트와의 연계 강화


결론

React Hooks는 함수형 컴포넌트에서 상태 관리, 사이드 이펙트 처리, 성능 최적화, 비동기 처리 등을 간결하게 구현할 수 있도록 돕는 강력한 도구입니다. React 19에서는 더욱 강력한 훅들이 추가되었으며, 앞으로도 계속해서 발전할 것으로 예상됩니다.

728x90
반응형