본문 바로가기

useEffect

1. useEffect

useEffect는 React 컴포넌트에서 부수 효과(side effects)를 처리하기 위한 Hook입니다. 부수 효과란 컴포넌트의 주요 기능인 화면 렌더링 외에 필요한 작업들을 의미합니다.

1.1 기본 구조

useEffect(콜백함수, 의존성배열);

useEffect는 두 개의 인자를 받습니다. 첫 번째는 실행할 콜백 함수, 두 번째는 의존성 배열입니다. 의존성 배열의 값이 변경되면 콜백 함수가 실행됩니다.

1.2 의존성 배열

useEffect의 실행 시점은 의존성 배열의 형태에 따라 달라집니다.

1) 빈 배열 [] - 마운트 시 1회 실행

import { useState, useEffect } from 'react';

function App() {
  const [count1, setCount1] = useState(0);
  const [count2, setCount2] = useState(0);

  useEffect(() => {
    console.log('처음에 한 번 실행되는 함수야!');
  }, []);

  return (
    <div>
      <h1>count1 : {count1}</h1>
      <button onClick={() => setCount1(count1 + 1)}>count1 증가</button>

      <h1>count2 : {count2}</h1>
      <button onClick={() => setCount2(count2 + 1)}>count2 증가</button>
    </div>
  );
}

컴포넌트가 처음 렌더링될 때 한 번만 실행됩니다. 초기 데이터 로딩 등에 적합합니다.

2) 특정 상태 감시 [state]

import { useState, useEffect } from 'react';

function App() {
  const [count1, setCount1] = useState(0);
  const [count2, setCount2] = useState(0);

  useEffect(() => {
    console.log('count1만 감시하는 함수야!');
  }, [count1]);

  return (
    <div>
      <h1>count1 : {count1}</h1>
      <button onClick={() => setCount1(count1 + 1)}>count1 증가</button>

      <h1>count2 : {count2}</h1>
      <button onClick={() => setCount2(count2 + 1)}>count2 증가</button>
    </div>
  );
}

count1 버튼을 클릭하면 콘솔에 메시지가 출력되지만, count2 버튼을 클릭하면 출력되지 않습니다.

3) 의존성 배열 생략 - 매 렌더링마다 실행

useEffect(() => {
  console.log('매 렌더링마다 실행');
});

모든 상태 변화에 반응합니다. 단, 무한 루프 위험이 있으므로 주의가 필요합니다.

1.3 클린업 함수

클린업 함수는 useEffect 내에서 반환되는 함수입니다. 컴포넌트가 언마운트되거나 다음 effect가 실행되기 전에 호출됩니다.

function Timer() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    const timer = setInterval(() => {
      setCount((c) => c + 1);
    }, 1000);

    // 클린업 함수: 타이머 정리
    return () => {
      clearInterval(timer);
    };
  }, []);

  return <h1>{count}</h1>;
}

클린업 함수가 없다면 컴포넌트가 사라져도 타이머가 계속 실행되어 메모리 누수가 발생합니다. clearInterval을 호출하여 타이머를 정리합니다.

1.4 함수형 업데이트

의존성 배열을 최적화하려면 함수형 업데이트를 활용합니다.

// ❌ count가 변경될 때마다 interval 재설정
useEffect(() => {
  const interval = setInterval(() => {
    setCount(count + 1);
  }, 1000);
  return () => clearInterval(interval);
}, [count]);

// ✅ 함수형 업데이트로 의존성 제거
useEffect(() => {
  const interval = setInterval(() => {
    setCount((prev) => prev + 1);
  }, 1000);
  return () => clearInterval(interval);
}, []);

함수형 업데이트 (prev) => prev + 1를 사용하면 항상 최신 값을 참조할 수 있어 의존성 배열을 비울 수 있습니다.

2. [실습] useEffect 사용해보기

SNS 게시글 작성 폼을 구현해보세요. 글자 수 제한과 임시저장 기능이 있는 게시글 작성 컴포넌트를 만들어봅시다.

[기본 코드]

function PostForm() {
  const [content, setContent] = useState('');
  const [charCount, setCharCount] = useState(0);
  const [warningMessage, setWarningMessage] = useState('');
  const maxLength = 150;

  // 여기에 코드를 작성하세요
  // 1. 글자 수 계산과 경고 메시지 표시를 위한 useEffect
  // 2. 임시저장을 위한 useEffect

  return (
    <div className="post-form">
      <h2>게시글 작성하기</h2>
      <textarea
        value={content}
        onChange={(e) => setContent(e.target.value)}
        placeholder="오늘 어떤 일이 있었나요?"
        maxLength={maxLength}
      />
      <div className="char-count">
        {charCount}/{maxLength}
      </div>
      {warningMessage && <p className="warning">{warningMessage}</p>}
      <button disabled={charCount === 0 || charCount > maxLength}>
        게시하기
      </button>
    </div>
  );
}

[요구사항]

  1. 실시간 글자 수 표시

    • 글자 수에 따른 경고 메시지 (0자: "내용을 입력해주세요", 150자: "최대 글자 수 입니다")
  2. 임시저장 기능

    • 내용 변경 시 localStorage에 자동 저장
    • 페이지 새로고침 시 저장된 내용 불러오기

3. [실습] 클린업 함수 사용해보기

검색어를 입력하면 일정 시간 후에 결과를 필터링하는 디바운싱을 구현하세요.

[기본 코드]

const items = [
  { id: 1, name: 'MacBook Pro' },
  { id: 2, name: 'MacBook Air' },
  { id: 3, name: 'iMac' },
  { id: 4, name: 'Mac Mini' },
  { id: 5, name: 'Mac Pro' },
];

function SearchComponent() {
  const [searchTerm, setSearchTerm] = useState('');
  const [results, setResults] = useState(items);

  // 여기에 코드를 작성하세요

  return (
    <div>
      <input
        type="text"
        value={searchTerm}
        onChange={(e) => setSearchTerm(e.target.value)}
        placeholder="검색어를 입력하세요"
      />
      <ul>
        {results.map((item) => (
          <li key={item.id}>{item.name}</li>
        ))}
      </ul>
    </div>
  );
}

[고려 사항]

  1. 타이핑할 때마다 즉시 검색하면 성능 저하 발생
  2. 타이핑 종료 후 500ms 후에 검색 실행
  3. 새로운 검색어 입력 시 이전 타이머 정리 (클린업 함수 활용)