개발새발 로그

React - useEffect에 대하여 본문

React

React - useEffect에 대하여

이즈흐 2023. 12. 11. 17:06

useEffect

마운트 되었을 때

업데이트 되었을 때

언마운트 되었을 때

와 같은 상황에 특정 작업을 실행하고 싶을 때 사용한다!

 

useEffect는 두 가지 형태로 작성한다.

useEffect(setup, dependencies?)

 

첫 번째로 useEffect의 인자로 콜백함수만 작성했을 때,

두 번째로 useEffect의 인자로 콜백함수와 의존성  배열을 받았을 때 이다.

콜백함수란?
함수의 파라미터로 들어가는 함수
용도는 보통 순차적으로 실행하고 싶을 때 쓴다.
쓰는 이유는 함수를 커스텀할 수 있다. - 파라미터로 받아온 함수에 따라

 

 

콜백함수만 작성했을 때에는 

렌더링 될 때마다 콜백함수가 실행이 된다.

 

콜백함수와 의존성 배열을 작성했을 때에는

첫 렌더링 되었을 때와 value값이 바뀔때 실행된다.

-> 만약 빈 배열이 작성되었을 때는 첫 렌더링 되었을 때만 실행이 된다.

 

 

실제로 어떻게 쓰이나?

useState를 사용하는 환경에서

만약 useState로 지정한 값이 변경되면 화면은 렌더링하게 되고, 

useEffect안의 콜백 함수도 계속해서 실행된다.

 

이때 컴포넌트에서 무거운 작업을 하게된다면 useEffect의 콜백함수는 계속해서 실행될 것이다.

이 때 의존성 배열(Dependency Array)을 사용하는 것이다.

 

이 안에 useState로 만든 변수를 넣게되면 해당 변수가 바뀔 때만 실행이 된다.

//렌더링마다 매번 실행 - 렌더링 이후
useEffect(()=>{
	// ...
});

//마운팅 + count가 변화할 때마다 실행됨
useEffect(()=>{
	// ...
},[count]);

//처음 마운팅되었을 때만 실행됨
useEffect(()=>{
	// ...
},[]);

 

 

Clean Up이란?

더이상 필요없는 타이머나 이벤트리스너를 정리해주는 작업이 필요하다.

return () => {함수} 로 사용한다.

useEffect(() => {
    // ...
    return () => {
      connection.disconnect();
    };
  }, []);
  // ...
}

 

return 안의 함수

해당 컴포넌트가 언마운트될 때

다음 렌더링시 useEffect가 실행되기 이전에 실행된다.

 

Clean Up을 사용하는 이유는?

export default Timer=()=>{
   useEffect(()=>{
     const timer = setInterval(()=>{
        console.log("타이머 돌아가는 중...");
     },1000);
   },[]);
}

 

- 위 useEffect를 Timer라는 컴포넌트에 넣고, 이를 App이라는 최상위 컴포넌트에서 호출해보자

export default const App=()=>{
  const [showTimer,setShowTimer] = useState(false);
  return(
    <div>
    	{showTimer && <Timer/>}
        <button onclick={()=>setShowTimer(!showTimer)}>Toggle</button>
    </div>
  )

}

 

- App에서는 토글버튼을 이용해서 Timer 컴포넌트를 나타냈다가 없앨 수 있다.

- 이를 실행해보면 

 Timer가 호출되었을 때 Timer의 useEffect안의 타이머 콜백함수가 실행된다.

 근데 다시 Toggle해서 Timer 컴포넌트를 unMount 했을 때 Timer안의 타이머 콜백함수도 멈춰야지 정상이다.

 하지만 unMount 했음에도 Timer가 돌아가게 된다.

 이 때 CleanUp을 사용하는 것이다.

export default Timer=()=>{
   useEffect(()=>{
     const timer = setInterval(()=>{
        console.log("타이머 돌아가는 중...");
     },1000);
     
     return ()=>{
        clearInterval(timer);
     }
   },[]);
}

 

 

반응형 종속성 지정

useEffect에서 사용하는 모든 반응형 값은 종속성(의존성)으로 선언해야 한다.

이펙트의 종속성 목록은 주변 코드에 의해 결정된다.

function ChatRoom({ roomId }) { //  이것은 반응형 값입니다.
  const [serverUrl, setServerUrl] = useState('https://localhost:1234'); //이것도 반응형 값입니다.

  useEffect(() => {
    const connection = createConnection(serverUrl, roomId); //  이 이펙트는 다음과 같은 리액티브 값을 읽습니다.
    connection.connect();
    return () => connection.disconnect();
  }, [serverUrl, roomId]); // ✅ 따라서 이펙트의 종속성으로 지정해야 합니다.
  // ...
}

 

*반응형값에는 프로 퍼티와 컴포넌트 내부에서 직접 선언된 모든 변수 및 함수가 포함된다.

roomId와 serverUrl은 반응형 값이므로 종속성에서 제거할 수 없다.

이 값을 생략하려고 할 때 린터가 React에 대해 올바르게 구성되면 린터는 이를 수정해야 하는 실수로 플래그를 지정한다.

function ChatRoom({ roomId }) {
  const [serverUrl, setServerUrl] = useState('https://localhost:1234');
  
  useEffect(() => {
    const connection = createConnection(serverUrl, roomId);
    connection.connect();
    return () => connection.disconnect();
  }, []); // 🔴  React Hook 사용 효과에 누락된 종속성인 'roomId'와 'serverUrl'이 있습니다.
  // ...
}

의존성을 제거하려면 해당 컴포넌트가 의존성이 될 필요가 없다는 것을 린터에게 "증명"해야 한다.

예를 들어 serverUrl을 컴포넌트 밖으로 이동하여 리액티브하지 않고 리렌더링 시 변경되지 않음을 증명할 수 있다.

const serverUrl = 'https://localhost:1234'; // 더 이상 반응형 값이 아닙니다.

function ChatRoom({ roomId }) {
  useEffect(() => {
    const connection = createConnection(serverUrl, roomId);
    connection.connect();
    return () => connection.disconnect();
  }, [roomId]); // ✅ 선언된 모든 종속성
  // ...
}

이제 serverUrl은 반응형 값이 아니므로(그리고 다시 렌더링할 때 변경할 수 없으므로) 종속성이 될 필요가 없습니다.

이펙트의 코드가 반응형 값을 사용하지 않는다면 종속성 목록은 비어 있어야 합니다([])

const serverUrl = 'https://localhost:1234'; // 더 이상 반응형 값이 아닙니다.
const roomId = 'music'; // 더 이상 반응형 값이 아닙니다.

function ChatRoom() {
  useEffect(() => {
    const connection = createConnection(serverUrl, roomId);
    connection.connect();
    return () => connection.disconnect();
  }, []); // ✅ 선언된 모든 종속성
  // ...
}

 

*참고

*반응형값에는 프로 퍼티와 컴포넌트 내부에서 직접 선언된 모든 변수 및 함수가 포함?
useEffect 훅에서 종속성 배열을 명시하지 않으면 다양한 문제가 발생할 수 있습니다.
종속성 배열을 명시하는 것은 이펙트의 동작을 명확하게 정의하고 특정 조건에서만 이펙트가 실행되도록 하는데 중요합니다.

종속성을 명시하지 않을 경우에 발생할 수 있는 주요 문제들은 다음과 같습니다:

1. 무한 루프 (Infinite Loop):
종속성 배열이 없는 경우, 이펙트 내에서 상태가 변경되어 컴포넌트가 리렌더링되면 이펙트가 다시 실행됩니다. 이때, 다시 상태를 변경하는 로직이 포함되어 있으면 무한 루프가 발생할 수 있습니다.
2. 성능 저하:
종속성을 명시하지 않으면 이펙트는 모든 렌더링에서 호출되기 때문에 필요없는 작업이 불필요하게 반복될 수 있습니다. 이는 성능에 부정적인 영향을 미칠 수 있습니다. 
3. 의도하지 않은 부수 효과:
종속성을 명시하지 않으면, 이펙트는 모든 렌더링에서 실행되기 때문에 예상치 못한 상태나 프로퍼티 값으로 인해 부수 효과가 발생할 수 있습니다.

 

 

이펙트의 이전 상태를 기반으로 상태 업데이트 하기

이펙트의 이전 상태를 기반으로 상태를 업데이트하려는 경우 문제가 발생할 수 있다.

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

  useEffect(() => {
    const intervalId = setInterval(() => {
      setCount(count + 1); // 매초마다 카운터를 증가시키려는 경우...
    }, 1000)
    return () => clearInterval(intervalId);
  }, [count]); // 🚩 ... 하지만 종속성으로 'count'를 지정하면 항상 interval이 재설정됩니다.
  // ...
}

 

count는 반응형 값이므로 종속성 목록에 지정해야 합니다.

그러나 이렇게 하면 count가 변경될 때마다 이펙트를 정리하고 다시 설정해야 한다.

이는 이상적이지 않습니다.

 

이 문제를 해결하려면 c => c + 1 상태 업데이터를 setCount로 전달해야한다. 

import { useState, useEffect } from 'react';

export default function Counter() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    const intervalId = setInterval(() => {
      setCount(c => c + 1); // ✅ 상태 업데이터 전달
    }, 1000);
    return () => clearInterval(intervalId);
  }, []); // ✅ 이제 count는 종속성이 아니다.

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

이제 count + 1 대신 c => c + 1 전달하므로 효과는 더 이상 카운트에 의존할 필요가 없다..

이 수정으로 카운트가 변경될 때마다 interval을 다시 정리하고 설정할 필요가 없다.

 

 

 

 

불필요한 객체 종속성 제거하기

효과가 렌더링 중에 생성된 객체 또는 함수에 의존하는 경우 너무 자주 실행될 수 있다.

예를 들어, 아래의 코드에서 useEffect는 렌더링할 때마다 options 객체가 달라지기 때문에 렌더링할 때마다 다시 연결된다.

const serverUrl = 'https://localhost:1234';

function ChatRoom({ roomId }) {
  const [message, setMessage] = useState('');

  const options = { // 🚩 이 오브젝트는 렌더링할 때마다 처음부터 새로 만들어집니다.
    serverUrl: serverUrl,
    roomId: roomId
  };

  useEffect(() => {
    const connection = createConnection(options); // 이펙트 내부에서 사용됩니다.
    connection.connect();
    return () => connection.disconnect();
  }, [options]); // 🚩  결과적으로 이러한 종속성은 리렌더링할 때마다 항상 달라집니다.
  // ...

 

렌더링 중에 생성된 객체를 종속성으로 사용하면 안된다. 대신 useEffect내에서 객체를 생성하면된다.

useEffect(() => {
    const options = {
      serverUrl: serverUrl,
      roomId: roomId
    };
    const connection = createConnection(options);
    connection.connect();
    return () => connection.disconnect();
  }, [roomId]);

이제 효과 내부에 options 객체를 만들었으므로 효과 자체는 roomId 문자열에만 의존한다.

 

불필요한 함수 종속성 제거하기

Effect가 렌더링 중에 생성된 객체 또는 함수에 의존하는 경우 너무 자주 실행될 수 있다.

예를 들어, 이 효과는 렌더링할 때마다 createOptions 함수가 다르기 때문에 렌더링할 때마다 다시 연결된다.

function ChatRoom({ roomId }) {
  const [message, setMessage] = useState('');

  function createOptions() { // 🚩 이 함수는 렌더링할 때마다 처음부터 새로 만들어집니다.
    return {
      serverUrl: serverUrl,
      roomId: roomId
    };
  }

  useEffect(() => {
    const options = createOptions(); //  이펙트 내부에서 사용됩니다.
    const connection = createConnection();
    connection.connect();
    return () => connection.disconnect();
  }, [createOptions]); // 🚩 결과적으로 이러한 종속성은 리렌더링할 때마다 항상 달라집니다.
  // ...

 

렌더링할 때마다 함수를 처음부터 새로 만드는 것은 문제가 되지 않는다.

즉 최적화할 필요가 없다.

하지만 이 함수를 이펙트의 종속성으로 사용하면 이펙트가 다시 렌더링할 때마다 다시 실행된다.

렌더링 중에 생성된 함수를 종속성으로 사용하지 말고, 대신 useEffect 안에서 선언하면 된다.

useEffect(() => {
    function createOptions() {
      return {
        serverUrl: serverUrl,
        roomId: roomId
      };
    }

    const options = createOptions();
    const connection = createConnection(options);
    connection.connect();
    return () => connection.disconnect();
  }, [roomId]);

 

이제 효과 내에서 createOptions 함수를 정의하면 효과 자체는 roomId 문자열에만 의존합니다.

다시 생성되는 함수와 달리 roomId와 같은 문자열은 다른 값으로 설정하지 않는 한 변경되지 않습니다.

 

 

 

출처

https://www.youtube.com/watch?v=kyodvzc5GHU

 

728x90
반응형
LIST