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