5 분 소요

리액트 스터디3 - useRef, useEffect

1. useEffect란?

1. useEffect의 목적

  • 부수 효과 관리: useEffect를 사용하여 컴포넌트에서 발생하는 부수 효과를 관리할 수 있다. 이는 데이터 가져오기, 구독 설정, DOM 조작, 타이머 설정 등과 같은 작업을 포함한다.

  • 컴포넌트 생명주기 연동: useEffect를 사용하여 컴포넌트의 생명주기와 관련된 작업을 수행할 수 있다. 예를 들어, 컴포넌트가 마운트될 때, 업데이트될 때, 혹은 언마운트될 때 특정 작업을 실행할 수 있다.

  • 상태와 props 감시: useEffect를 사용하여 특정 상태나 props의 변화를 감지하고, 이에 따라 특정 동작을 수행할 수 있다. 이를 통해 상태나 props의 변경에 따른 반응형 UI를 구현할 수 있다.



2. useEffect의 이점

  • 코드 분리와 모듈화: 부수 효과 관련 로직을 useEffect 안에 넣음으로써, 컴포넌트의 코드를 간결하게 유지하고 부수 효과 관련 로직을 모듈화할 수 있다.

  • 생명주기 관리의 용이성: useEffect를 사용하여 컴포넌트의 생명주기와 관련된 작업을 효율적으로 관리할 수 있다. 이는 코드의 가독성을 높이고, 컴포넌트의 동작을 예측 가능하게 만든다.

  • 성능 최적화: useEffect를 사용하여 필요한 시점에만 부수 효과를 실행하도록 설정함으로써, 불필요한 작업을 피하고 애플리케이션의 성능을 최적화할 수 있다.



3. useEffect 추가 설명

  • 비동기: setter 함수를 호출 한 즉시에는 useEffect의 로직 함수가 호출되지는 않는다.
  • 동기: useEffect가 여러개 작성되어 있는 경우에는 제일 먼저 작성 되어 있는 useEffect 부터 제일 마지막에 작성되어 있는 useEffect 순으로 순서대로 호출된다.



2. useEffect의 동작 원리

1. 컴포넌트의 생명주기

  • 화면에 추가될 때 마운트
  • 새로운 props나 state를 수신하면 업데이트
  • 화면에서 제거되면 언마운트


2. Effect의 생명주기

  • useEffect는 React 컴포넌트의 렌더링 주기 중 특정 시점에 실행되는 함수
  • useEffect는 컴포넌트가 마운트될 때, 업데이트될 때, 혹은 언마운트될 때 실행되는데, 이때마다 부수 효과를 처리할 수 있다.
  • useEffect는 기본적으로 컴포넌트가 마운트될 때와 업데이트될 때마다 실행된다.
  • 이때, useEffect의 의존성 배열을 통해 특정 상태나 props의 변화를 감지하여 실행 여부를 결정할 수 있다.
  • useEffect의 의존성 배열에 명시된 값들이 변경될 때에만 useEffect가 다시 실행된다.
import { useState, useEffect } from "react";

function ComponentLifecycleExample(): JSX.Element {
  // 마운트: 1번째 실행 (콘솔 출력, state 초기화)
  console.log("컴포넌트 내부 코드를 실행합니다.");
  const [count, setCount] = useState < number > 0;

  useEffect(() => {
    // 마운트 및 업데이트: 3번째 실행
    console.log("컴포넌트가 렌더링 또는 업데이트 되었습니다.");
    return () => {
      // 언마운트: 4번째 실행(클린업 함수)
      console.log("컴포넌트가 언마운트되기 전에 호출됩니다.");
    };
  }, [count]);

  return (
    <>
      {/* 2번째 실행 (렌더링) */}
      <p>Count: {count}</p>
      <button
        onClick={() =>
          setCount((prev) => {
            return prev + 1;
          })
        }
      >
        증가
      </button>
    </>
  );
}

export default ComponentLifecycleExample;



3. useEffect의 사용법

Effect

  • 특정 이벤트가 아닌 렌더링에 의해 직접 발생한다.
  • 서버 연결 설정은 어떤 상호 작용과도 상관없이 발생해야 하므로 Effect이다.
  • 커밋 단계가 끝난 후 화면 업데이트가 이루어지고 나서 실행된다.
  • 이 시점이 React 컴포넌트를 외부 시스템과 동기화하기 좋은 타이밍이다.
  • 이 안에는 setter함수가 아닌 순수 함수만 있어야한다.


의존성 배열

  • 의존성 배열이 없는 경우

    useEffect(() => {
      // 모든 렌더링 후에 실행
    });
    
  • 의존성 배열이 비어있는 경우

    useEffect(() => {
      // 마운트될 때만 실행 (컴포넌트가 나타날 때)
    }, []);
    
  • 의존성 배열이 있는 경우

    useEffect(() => {
      // 마운트될 때 실행
      // 렌더링 이후에 a 또는 b 중 하나라도 변경된 경우에도 실행
    }, [a, b]);
    



4. useEffect의 활용

  • 데이터 가져오기
  • 구독 설정
  • 이벤트 처리
import React, { useState, useEffect } from "react";

function ExampleComponent() {
  const [data, setData] = useState(null);

  // 데이터 가져오기
  useEffect(() => {
    fetchData(); // 데이터 가져오기 함수 호출
  }, []);

  // 구독 설정
  useEffect(() => {
    const subscription = subscribeToData((data) => {
      setData(data);
    });

    return () => {
      subscription.unsubscribe(); // 구독 해제
    };
  }, []);

  // 이벤트 처리
  useEffect(() => {
    const handleClick = () => {
      console.log("Button clicked!");
    };

    document.addEventListener("click", handleClick);

    return () => {
      document.removeEventListener("click", handleClick); // 이벤트 리스너 제거
    };
  }, []);

  // 타이머 설정
  useEffect(() => {
    const timer = setTimeout(() => {
      console.log("Timer expired!");
    }, 5000);

    return () => {
      clearTimeout(timer); // 타이머 해제
    };
  }, []);

  return (
    <div>
      <h2>Example Component</h2>
      {/* 데이터 표시 및 이벤트 핸들링 */}
      <button onClick={() => fetchData()}>Fetch Data</button>
      {data && <p>Data: {JSON.stringify(data)}</p>}
    </div>
  );
}

export default ExampleComponent;



5. useEffect 주의할 점

1. Effect가 두 번 실행되는 경우

  • Strict Mode를 켜두면 개발 환경에서만 컴포넌트를 다시 마운트하여 두 번 실행된다.
  • 첫 번째 연결이 종료되지 않은 채 두 번째 연결이 설정되었기 때문이다.
  • Strict Mode는 켜두고 Effect 함수 내에서 클린업 함수를 구현하는 것을 권장한다.
  • 클린업 함수를 사용하면 메모리 누수를 해결할 수 있다.

클린업 함수 예시

useEffect(() => {
  const connection = createConnection();
  connection.connect();
  return () => connection.disconnect(); //클린업 함수
}, []);



2. Effect가 필요하지 않은 경우

  • 렌더링 중에 무언가를 계산할 수 있을 때
  • props나 state로 계산 가능할 때
  • 이벤트 핸들러에 함수 추가할 때



1) 렌더링을 위해 데이터를 변환할 때

  • 컴포넌트의 최상위 레벨에서 모든 데이터를 변환한다.
  • 그러면 props나 state가 변경될 때마다 해당 코드가 자동으로 다시 실행된다.


틀린 코드

function Form() {
  const [firstName, setFirstName] = useState("Taylor");
  const [lastName, setLastName] = useState("Swift");

  // 🔴 중복된 state 및 불필요한 Effect
  const [fullName, setFullName] = useState("");
  useEffect(() => {
    setFullName(firstName + " " + lastName);
  }, [firstName, lastName]);
  // ...
}


올바른 코드

function Form() {
  const [firstName, setFirstName] = useState("Taylor");
  const [lastName, setLastName] = useState("Swift");
  // 렌더링 중에 계산됨
  const fullName = firstName + " " + lastName;
  // ...
}



2) 사용자 이벤트를 핸들링할 때

  • 사용자가 제품을 장바구니에 넣을 때마다 알림을 표시하고 싶다.
  • useEffect로 코드 중복을 제거해야할까?
  • 핸들러 함수에서 매번 해당 함수를 호출해야할까?
  • 후자가 맞다.


  • 이벤트 핸들러는 버튼 클릭과 같이 항상 “수동으로” 트리거 되지만,
  • Effect는 동기화 유지에 필요한 만큼 자주 실행 및 재실행되기 때문에 “자동으로” 트리거 된다.


틀린 코드

function ProductPage({ product, addToCart }) {
  // 🔴 Effect 내부의 이벤트별 로직
  useEffect(() => {
    if (product.isInCart) {
      showNotification(`Added ${product.name} to the shopping cart!`);
    }
  }, [product]);

  function handleBuyClick() {
    addToCart(product);
  }

  function handleCheckoutClick() {
    addToCart(product);
    navigateTo("/checkout");
  }
  // ...
}


올바른 코드

function ProductPage({ product, addToCart }) {
  // ✅ 이벤트 핸들러에서 이벤트별 로직 호출
  function buyProduct() {
    addToCart(product);
    showNotification(`Added ${product.name} to the shopping cart!`);
  }

  function handleBuyClick() {
    buyProduct();
  }

  function handleCheckoutClick() {
    buyProduct();
    navigateTo("/checkout");
  }
  // ...
}



3. 무한 루프

useEffect(() => {
  setCount(count + 1); // 무한 루프 발생
}, [count]);



6. useLayoutEffect

  • 대부분의 Side Effect는 useEffect를 통한 비동기 처리가 권장된다.
  • 하지만 DOM의 업데이트로 인해 발생하는 화면 깜빡임은 사용자 경험에 좋지 않다.
  • 이러한 상황을 만났을 때 useLayoutEffect가 좋은 해결책이 될 수 있다.
  • 로직이 복잡할 경우 사용자가 레이아웃을 보는데까지 시간이 오래걸린다는 단점이 있어, 기본적으로는 항상 useEffect 만을 사용하는 것을 권장한다.

1. useEffect

  • useEffect는 컴포넌트가 render와 paint까지 된 후에 비동기적으로 실행된다.
  • 그래서 useEffect 내부에 DOM에 영향을 주는 코드가 있다면 화면 깜빡임이 생길 수 있다.


2. useLayoutEffect

  • useLayoutEffect는 컴포넌트가 render된 후에 동기적으로 실행된다.
  • 그 후 마지막으로 paint가 실행된다.
  • 그래서 useLayoutEffect 내부에 DOM에 영향을 주는 코드가 있어도 화면 깜빡임이 생기지 않는다.

image

카테고리:

업데이트:

댓글남기기