[React 완벽 가이드] Section 11: side Effect 다루기 & useEffect()

gonn-i·2024년 6월 13일
0

React 완벽 가이드

목록 보기
8/18
post-thumbnail

본 포스트는 Udemy 리액트 완벽가이드 2024 를 듣고 정리한 내용입니다.

목차 🌳
1️⃣ Side Effects를 이해하고, 왜 제어해야 하는지 이해 💬
2️⃣ useEffectDependencies
3️⃣ useEffect를 사용하면 안 되는 상황!🙅🏻‍♀️

Section 11

💡 Side Effect

side Effect 가 뭔데요

주된 효과 외에 부가적으로 발생하는 효과 (부수효과)
이때 리액트의 주된 역할이라면 함은 컴포넌트를 랜더링하는 것 (UI 얍 짜잔)
그래서 state를 통한 랜더링과, event를 발생시켰을때 상황에 따른 랜더링으로 UI가 바뀌는것이 주요소이다.
(ex. 버튼 클릭 / input에 텍스트 입력해서 화면에 보여주기 등 )
이때 부수효과라 함은, 이것들을 제외한 나머지 모든 react 앱에서 발생하는 모든 것을 의미함!!!

HTTP Req (API 요청이나) 웹 스토리지 저장 (local Storage, indexedDB) 등 리액트에서 돌아가는 작업이지만, 부수효과로 고려된다.

그냥 함수 돌아가게 실행해두고, state로 관리하면 안되나요?

만약 상태가 업데이트 되어 재랜더링을 하게 되면, 내부에 관련 컴포넌트를 다시 실행하게 되는데 그러면
(1) 함수를 통한 상태 변경 -> (2) 관련 컴포넌트 재랜더링 -> (3) 그럼 그 컴포넌트에 들어있던 (1) 함수 다시 실행 -> (2) ~ (3) 무한 반복

그럼 부수효과 어떻게 다루는데요? 🤯

💡 useEffect

side Effect을 수행하기 위한 Hook!
데이터 가져오기, 구독(subscription) 설정하기, 수동으로 React 컴포넌트의 DOM을 수정하는 것까지 이 모든 것(side Effect)을 useEffect를 통해서 처리 👀

useEffect도 react 훅이지만, useState와 useRef 와는 다르게 값을 반환하지는 않음

사용방법 👀
useEffect(function, dependency)
function : useEffect을 통해 실행할 side Effect 함수
dependency : [] 배열 형태로, ✨의존성 값이 변화했을 때 Effect 안에 function을 재실행✨시킨다.

🔄 실행 시점의 차이 (최초 랜더링 vs 재랜더링)

useEffect의 최초 실행: 해당 컴포넌트가 실행을 모두 완료한 이후 (JSX 코드 반환된 후의 시점)에서야 => useEffect에 전달한 부수효과를 담은 함수 function 을 실행 시킴
재실행은 언제? : 2번째 인자로 넘긴, dependency의 값이 업데이트 될때에만 재랜더링!!!

🫥 만약, dependency에 빈배열이 들어간 경우,

최초 랜더링일 때만 작동하겠다는 의미! 의존성을 가진 배열이 없으니 다시 재랜더링할 기준이 없어졌으니, 재랜더링 기회 ❌

🙅🏻‍♀️ 만약, dependency에 아무것도 전달하지 않으면?

이건 무한루프에 빠지게 되는 결과를 초래!!

🙅🏻‍♀️ 만약, dependency에 함수를 넣으면?

js에서 함수는 객체로 인지하게 되는데, 이때 함수가 실행될때 새로운 객체(값이)가 생성되게 된다.
따라서, 무한 루프에 빠질 가능성이 농후
(재랜더링할떄마다, 새로운 값이 나오는데 그럼 의존성 때문에 재랜더링되고, 또 새로운 값이 나오고의 반복)

함수를 정 쓰고 싶다면, useEffect를 쓰는 컴포넌트를 DOM에서 제거하던가 하는데, 굳이다.

💡 useCallback

함수를 의존성 배열안에 넣어서, useEffect을 사용하고 싶을때 방법이 있다. 소제목에서도 알 수 있듯이 바로 useCallback 를 사용하는 방법이다.

useCallback 은 인자로 전달한 콜백 함수 자체를 메모이제이션하여, 함수가 재생성되지 않도록 성능을 최적화한다! 따라서, 특정 상황에서 불필요한 함수 재생성을 방지해, 불필요한 랜더링을 막아준다!!!

함수가 다시 랜더링되어야 할 때(callback 에 대한 의존성에 의해 재랜더링되는 경우가 아닌), 다시 돌려서 함수에 대한 값을 새로 생성하는 것이 아닌, 메모리에 이미 저장되어 기록한 값을 가져와서 재사용한다.

사용방법
useCallback (함수, [의존성])

과한, useEffect 사용은 자제!!!

기본적으로 컴포넌트를 돌리는 것 외에, 추가로 side Effect 함수를 돌리는거라 랜더링 속도를 늘려주어 사용성 측면에서 부정적일 수 있음

🙅🏻‍♀️ 부수효과를 실행해야 하지만, useEffect가 필요하지 않은 상황 예시🙅🏻‍♀️
특정 컴포넌트가 돌아가면서 랜더링 되는 경우가 아닌!
1️⃣ event의 변화에 따라 실행되는 경우(사용자의 상호작용이 있어야만 작동하는 경우)에는 useEffect에 넣지 말고, function handleClick() {}` 과 같이 클릭시 발생하는 함수 안에서 넣어서 사용할것
2️⃣ 최초 랜더링시에 필요한 값이지만, 실행 이후 즉각적으로 바로 데이터를 가져올 수 있는 경우
해당 컴포넌트 밖에서 실행해서, 불필요한 퍼포먼스를 줄이기!
(ex. localStorage에 접근해서 데이터 가져오는 경우 <-> geolation으로 사용자의 현 위치 불러오는 경우)


타이머를 통한 찜한 여행지 삭제 기능 ⏰

다음의 모달에서 Yes 를 누르면 여행지가 삭제되고, NO를 누르면 모달만이 사라진다.
하지만, setTimeout 기능을 통해 모달이 보이게 되고 n초 뒤에 자동으로 삭제되는 기능을 만들고 싶다면 다음과 같은 코드를 생각해볼 수 있다.

export default function DeleteConfirmation({ onConfirm, onCancel }) {
  useEffect(() => {
    setTimeout(() => {
      onConfirm();
    }, 3000);
  }, []);
  
  // 혹은 
  // setTimeout(() => {
  //    onConfirm();
  //  }, 3000);
  // }


  return (
    <div id="delete-confirmation">
      <h2>Are you sure?</h2>
      <p>Do you really want to remove this place?</p>
      <div id="confirmation-actions">
        <button onClick={onCancel} className="button-text">
          No
        </button>
        <button onClick={onConfirm} className="button">
          Yes
        </button>
      </div>
    </div>
  );
}

이렇게 된다면, 3초 후에 자동적으로 삭제 기능이 작동하지만 만일
No 를 눌러 삭제를 원치 않을때에도, 타이머는 초기화되지 못하고 여행지를 삭제 시킨다.(의도치 못한 동작이 발생)

이경우, clean up 함수를 통해 컴포넌트가 언마운트 될때useEffect가 다시 실행되기 전에 타이머를 초기화시키는 행위가 필요하다.

if clean up 안 해준다면? 🥵

  • 타이머 해제: setInterval이나 setTimeout을 해제하지 않으면 컴포넌트가 제거된 후에도 타이머가 계속 실행
  • 이벤트 리스너 제거: addEventListener를 통해 등록한 이벤트 리스너를 제거하지 않으면 메모리 누수나 예기치 않은 동작 발생 가능
  • 구독 해제: 웹소켓 연결이나 데이터베이스 구독을 해제하지 않으면 불필요한 네트워크 트래픽이 발생 가능

언마운트 그게 뭔데요? ➡️ 컴포넌트의 생명주기부터 알아보아요

컴포넌트의 생명주기💡

1️⃣ Mount : 컴포넌트가 처음 DOM에 삽입될때 실행
2️⃣ Update : 컴포넌트의 상태(state) 나 속성(props)이 변경될때 실행
3️⃣ UnMount : 컴포넌트가 DOM 에서 제거될때 실행

컴포넌트의 언마운트 시기
1️⃣ 조건부 랜더링에서 제거될때
{showComponent && <MyComponent />}
2️⃣ 부모 컴포넌트가 언마운트될때 -> 자식들도 함께 제거됨
3️⃣ 리스트에서 제거될때

const components = items.map(item => <MyComponent key={item.id} />);
// items 배열이 변경되어 특정 item이 제거되면, 해당하는 MyComponent도 언마운트

clean up 함수 작성 방법

useEffect에서 clean up 함수를 return 해주면 된다.

타이머 clean up 코드

useEffect(() => {
    const timer = setTimeout(() => {
      onConfirm();
    }, 3000);

	// 	클린업 진행 (타이머 초기화)
    return () => {
      clearTimeout(timer);
    };
  }, []);

setTimeout

  // 잔여시간 -10  .. clean up이 없으면 계속 -10으로 무한루프
useEffect(() => {
  const interval = setInterval(() => {
    setRemainTime((prevTime) => prevTime - 10);
  }, 10);

  return () => {
    clearInterval(interval);
  };
}, []);

0개의 댓글