React 함수 컴포넌트의 상태 및 효과 제어 기법

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>;
});

태그: React Hook useState useEffect useRef

7월 7일 20:40에 게시됨