본문 바로가기

카테고리 없음

Side Effects & useEffect [React - The Complete Guide 2024]

Side effects are "tasks" that don't impact the current component render cycle.

 

사용자의 위치는 브라우저의 메소드로 부터 받을 수 있다. navigator.geolocation.getCurrentPosition 
position 객체를 통해 경도나 위도를 포함한 데이터를 넘겨준다.

이러한 작업들은 jsx를 반환하는 것이 아니며, 컴포넌트의 주 목적과는 다르기에, 부수효과 (side effect)라고 불린다.

 

useEffect는 반환값이 없으며, 2개의 인자를 필요로 한다.
1. 부수효과 + 묶어줄 함수
2. dependency(의존성) 배열
의존성 배열이 변할 때, 1 (Effect)를 실행한다.
빈 배열을 준다면, 최초 컴포넌트 실행 완료 후 1회만 실행되며, 아무것도 주지 않는다면, 무한 루프로 실행된다.

 

  useEffect(() => {
    navigator.geolocation.getCurrentPosition((position)=>{
      const sortedplaces = sortPlacesByDistance(
          AVAILABLE_PLACES,
          position.coords.latitude,
          position.coords.longitude
      );
      // useeEffect로 감싸주지 않는다면? 혹은 의존성 배열이 누락되면?
      // 사용자 위치 불러오기 -> 상태 업데이트 -> 컴포넌트 리렌더링 -> 사용자 위치 불러오기... 무한 루프
      setAvailablePlaces(sortedplaces);
    })
  }, []);

 


모든 부수효과에서 useEffect가 필요하지 않으며, useEffect의 과도한 사용은 좋지 않다.
 실행과정에서 앱 또는 컴포넌트 실행 후 추가적인 실행(의존성 체크)가 발생하기 때문이다.

getCurrentPosition에서 useEffect를 사용한 이유.
실행하는데 있어 callback함수도 작동을 해야하고, 실행하는데에 시간이 오래 걸리기에.

 

* 불필요한 useEffect의 예시

  useEffect(() => {
    const storedIds = JSON.parse(localStorage.getItem('selectedPlaces')) || [];
    const storedPlaces = storedIds.map( (id) =>
      AVAILABLE_PLACES.find((place)=> place.id === id));
    setPickedPlaces(storedPlaces);
  }, []);

함수 내부이기에 useEffect를 사용할 수 없을 뿐더러. 사용할 필요도 없다.
앱 컴포넌트 재실행시가 아닌, 해당 함수 handleSelectedPlace가 실행되었을 때, 이 코드도 실행됨. => 무한루프 x
브라우저 제공 객체 localStorage

const storedIds = JSON.parse(localStorage.getItem('selectedPlaces')) || [];
const storedPlaces = storedIds.map( (id) =>
    AVAILABLE_PLACES.find((place)=> place.id === id));

 

useEffect의 다른 용도

modal.jsx

import {forwardRef, useEffect, useImperativeHandle, useRef} from 'react';
import { createPortal } from 'react-dom';

function Modal({ open, children }) {
  console.log("Timer setup");
  const dialog = useRef();

  // 무한루프 방지가 아닌, 특정 값을 동기화 하고자 하는 useEffect
  useEffect(() => {
    // ui에 영향을 주지만 아래 jsx와는 연관x. 부수효과 (컴포넌트 렌더링과 직접 연관x)
    if(open) {
      dialog.current.showModal();
    }
    else {
      dialog.current.close();
    }
  }, [open]);


  return createPortal(
      // dialog의 속성 open
      // dialog.current.showMoal() 을 통해서만 backdrop추가 (회색 배경)
    <dialog className="modal" ref={dialog} onClose={onclose}>
      {open? children : null}
    </dialog>,
    document.getElementById('modal')
  );
}

export default Modal;

 

 

useEffect의 Cleanup 함수

// 브라우저 빌트 인 함수
useEffect(() => {
    console.log("Time set");
    const timer = setTimeout( () => {
        onConfirm();
    }, TIMER);
    // 언제나 DOM의 일부분이며, APP이 최초 렌더링시 타이머도 설정되고 시작한다. -< modallsOpen으로 조건부 렌더링

    //컴포넌트가 사라지기 전 (DOM에서 삭제되기 전) 실행됨. Effect함수가 최초로 작동되기 이전에는 실행되지 않음.
    return ()=> { // cleanup
        clearTimeout(timer);
    };
}, [onConfirm]);

종속성으로 함수를 넘겨줄 때는 무한루프를 주의해야 한다.

자바스크립트의 함수는 객체임을 인지하자. (앱 컴포넌트가 실행될때마다 재생성됨.)
 상태 업데이트 -> App.jsx 의 setModalIsOPen -> hadnleStopRemovePlace -> App 재실행
     -> 새로운 handleStopRemovePlace -> DeleteConfirmation으로 전달 -> Effect 다시 작동
하지만 여기서는..! DOM에서 자식 컴포넌트를 삭제하는 방식으로 동작하기에, 무한 루프에 빠지지 않는다.!

=> 함수의 재생성을 방지하기 위한 hook : useCallback

 

useCallback

useEffect와 달리 value를 return한다.

함수가 재생성되지 않고, 메모리로서 내부에 저장 후 재사용한다.

const handleRemovePlace = useCallback(  function handleRemovePlace() {
  setPickedPlaces((prevPickedPlaces) =>
      prevPickedPlaces.filter((place) => place.id !== selectedPlace.current)
  );
  setModalIsOpen(false);

  const storedIds = JSON.parse(localStorage.getItem('selectedPlaces')) || [];
  localStorage.setItem('selectedPlaces', JSON.stringify(storedIds.filter((id) => id !== selectedPlace.current))
  );

},[]); // 종속성에는 props나 state value 만이 가능

 

 

 

시간에 따른 진행 막대 표시하기 

import {useEffect, useState} from "react";

export default function ProgressBar({ timer }) {
    const [remainingTime, setRemainingTime] = useState(timer);

    useEffect(() => {
        console.log('setInterval');
        const interval = setInterval(() => {
            setRemainingTime(prevTime => prevTime - 10);
        }, 10);

        return () => {
            console.log('clear Interval');
            clearInterval(interval);
        };
    }, []);

    return <progress value={remainingTime} max={timer} />;
}

 


 

useEffect – React

The library for web and native user interfaces

react.dev