React Component 생명 주기 (useEffect)

2025. 4. 10. 12:56Coding Study/React

1. Componet 의 생명 주기

   1) Mount : 화면이 처음 렌더링 될 때

   2) Update : 화면이 리렌더링 될 때

   3) unMount : 화면이 제거 될 때 

 

3가지의 상황을 useEffect 를 활용하면 처리가 가능 하다.

 

2. useEffect

   1) 함수 기본 구조      

      (1) 은 화면에서 사라질때 실항할 코드를 작성한다.

      (2)의 의존성 배열은 재 랜더링 시에 값이 달라지면 useEffect 내부의 코드를 실행한다.

useEffect(() => {
  // 실행할 코드
  return () => {
    // 언마운트 시 실행할 정리(clean-up) 함수 (1)
  };
}, [의존성배열]);  // (2)

 

   return 부와 의존성 배열은 선택 사항으로 작성해도 되고 안해도 된다.

 

 

   2) 아무 배열도 전달하지 않을때 

useEffect(() => {
  // 실행할 코드
  
});

🔁 → 마운트 + 모든 리렌더링 시마다 실행됨

 

 

   3) 빈 배열 전달

useEffect(() => {
  console.log('처음 렌더링될 때만 실행됨');
}, []);

 

✅ → 한 번만 실행 (Mount 시점)

 

 

 

   4) 특정 값만 바뀔 때 실행

useEffect(() => {
  console.log('count가 바뀔 때만 실행됨');
}, [count]);

🌀 → count 값이 바뀔 때만 실행

 

 

 

 

  5) 컴포넌트 언마운트 시 정리 작업

useEffect(() => {
  console.log('컴포넌트 마운트됨');

  return () => {
    console.log('컴포넌트 언마운트됨');
  };
}, []);

🧹 → 처음 렌더링 시 실행 후, 화면에서 사라질 때 return 함수 실행

 

 

 

 

클린업 리턴 함수 작성 시 의존성 배열의 유무에서 작동 차이

 

 

 

의존성 배열 있을 시

useEffect(() => {
  return () => {
    console.log("언마운트");
  };
}, []);

이 경우에는 의존성 배열이 빈 배열이기 때문에, 해당 useEffect는 컴포넌트가 마운트될 때 한 번 실행되고, 그 안의 return 함수는 언마운트될 때만 실행됩니다. 즉, 컴포넌트 생애주기 중 "cleanup" 역할을 합니다.

재렌더링 (Update) 될 때에는 실행 되지 않는다. 

 

 

의존성 배열 없을 시

useEffect(() => {
  return () => {
    console.log("언마운트");
  };
});

 

이 경우에는 의존성 배열이 아예 없기 때문에, React는 이 useEffect를 모든 렌더링(업데이트) 후에 실행

그리고 그 전에 기존의 이펙트를 클린업하기 위해 return 함수를 먼저 실행

 <코드  동작 순서>

  1. 마운트 후   useEffect 실행
  2. 리렌더링 전 useEffect 의 return 실행
  3. 리렌더링 후 다시 useEffect 실행

                      .

                      .

                      .

   최종 unmount 시 return 부실행

 

패치시 useEffect을 자주 사용하는데 useState 의 초기 값으로 콜백에 패치를 적용해도 1번만 패치되는거니까 사용해도 되는거 아닌가?

 

useEffect 사용하여 데이터 fetch 시 일반적으로 아래와 같이 코드를 작성한다.

const [data, setData] = useState<SomeType[]>([]);

useEffect(() => {
  fetch("/api/data")
    .then((res) => res.json())
    .then(setData);
}, []);

여기서 "의존성 배열이 빈 []"이니까 컴포넌트 마운트 시 딱 한 번만 실행되고
 "그럼 useState의 초기값으로 fetch를 호출하면 똑같이 한 번만 실행되는 거 아닌가?" 라는 의문이 생김.

 

이론적으로는 아래와 같이 useState의 초기값으로 패치를 적용 가능 하다.

const [data, setData] = useState<SomeType[]>(() => {
  fetch("/api/data")
    .then((res) => res.json())
    .then((result) => {
      console.log(result);
    });
  return [];
});

 

 

⚠️ 하지만, 그렇게 하면 안 되는 이유

실무나 리액트 설계 철학에서 이 방식은 권장되지 않는다.

1. 데이터 패칭은 사이드 이펙트

  • React의 렌더링은 “순수해야” 한다.
    즉, 렌더링 시 외부 세계(네트워크, 로컬스토리지 등) 에 영향을 주면 안 된다는 규칙이 있다.
  • useState의 초기값 콜백은 렌더링 과정에서 실행되기 때문에,
    거기서 fetch를 호출하면 렌더링 → 네트워크 요청이라는 부작용(side effect) 이 생겨버린다.
  • 그래서 React 팀에서도 이런 사용은 안티패턴으로 본다.

2. StrictMode(개발 모드) 에서는 두 번 실행될 수 있다

  • React 18 이후 StrictMode가 활성화되어 있으면,
    렌더링 과정에서 일부 함수들이 의도적으로 두 번 호출돼서 “부작용 감지”를 한다.
  • 이때 useState의 lazy initializer 안에 fetch가 있으면,
    “한 번만 실행돼야 하는 네트워크 요청”이 두 번 발생할 수 있다 ❌

3. 에러 처리, 로딩 상태 관리가 어렵다

  • useEffect 내부에서는 loading, error 상태를 별도로 다루기 쉬운데,
    useState 초기화에서는 로딩 상태 분리나 cleanup을 하기 어렵다.

 

컴포넌트 내 직접 실행 과 useEffect 내부 실행의 차이

 

 

function Component() {
 console.log("렌더링됨")
}

 

function Component() {
 useEffect(()=>{
  console.log("렌더링됨")
  })
}

 

위의 두 코드의 차이점 

 1. useEffect 는 클라이언트 사이드에서 실행되는 것을 보장한다. 즉 useEffect 내부에서는 window 객체 접근이 가능하다.

 2. useEffect 는 컴포넌트 렌더링의 부수효과 이며 렌더링이 완료된 이후에 실행된다. 

 

useEffect 첫 번째 인수에 함수명 부여 하자

 

useEffect 의 수가 적거나 복잡성이 낮다면 익명함수를 사용해도 문제가 없다.

그러나 useEffect의 코드가 복잡하고 많아질수록 무슨 일을 하는 useEffect 코드인지 파악하기 힘들어진다.

이때 기명함수를 적용하면 useEffect 의 목적을 파악하기가 쉬워진다.

useEffect(
 function logActiveUser(){
   logging(user.id)
  }
 },[user.id])

 

useEffect 에 비동기 함수를 바로 넣을 수 없는 이유?

 

useEffect(async()=>{
  const response = await fetch('http://some.data.com')
  const result = await response.json()
  setData(result)
},[])

위의 코드를 실행 하면 에러가 발생한다.

⚠️ 참고: 왜 에러가 발생하는가?

다시 한번 상기시켜 드리자면, React의 $useEffect는 **클린업 함수(Cleanup Function)**를 반환할 수 있어야 하는데, async 함수는 항상 Promise를 반환합니다. React는 Promise를 유효한 클린업 함수로 인식하지 못하기 때문에 다음과 같은 경고(Warning) 또는 에러를 발생시킵니다.

Warning: An effect function must not return anything besides a function, which is used as its cleanup. It looks like you wrote 'async function' which returns a Promise.

 

만약 useEffect 가 비동기 함수가 사용 가능 하다면 비동기 함수의 응답속도에 따라 결과가 이상하게 나타날 수 있다.  첫번째 응답이 10초뒤에 나올 예정이고 두번째 응답이 1초가 걸린다면 응답이 늦게 나온 첫번째 값의 기반으로 결과가 나타날 수 있다.

이런 문제를 useEffect의 경쟁상태( race condtion ) 이라고 한다. 

 

useEffect 내의 올바른 비동기 함수 실행 방법

useEffect(() => {
  const fetchData = async () => { // 비동기 함수를 내부에서 정의
    const response = await fetch('http://some.data.com');
    const result = await response.json();
    setData(result);
  };
  fetchData(); // 즉시 호출

  // cleanup 함수를 반환할 수 있음 (선택 사항)
  // return () => { /* cleanup code */ }; 
}, []);

 

✨ 마무리 요약

useEffect(() => {}) 마운트 + 모든 리렌더링 시 실행
useEffect(() => {}, []) 마운트 시 딱 1번 실행
useEffect(() => {}, [value]) value 변경 시 실행
useEffect(() => { return () => {} }, []) 언마운트 시 실행

 

'Coding Study > React' 카테고리의 다른 글

SCSS - React 에서 스타일링 하기  (1) 2025.04.15
Custom Hook  (0) 2025.04.11
React Router 를 활용한 동물정보 사이트 만들기  (0) 2025.04.09
React Router  (0) 2025.04.09
useReducer  (0) 2025.04.08