React에서 useEffect를 정말 많이 사용하는 것을 볼 수 있다. 개념은 알았지만 "이게 왜 생겼는지", "무작정 써도 되는지" 알기 위해 공부한 내용을 정리해보도록 한다.
React는 렌더링이 순수 함수처럼 예측 가능하게 이뤄져야 한다
는 철학을 갖고 있다.
그러나, 렌더링 과정에서 side effect
가 발생하면 예측 불가능하고 관리가 어려워진다.
side effect는?
렌더링 외에 발생하는 모든 작업을 의미한다.
React의 주요 목적은 입력에 따라 화면 UI를 선언적으로 그리는 것인데, 컴포넌트에서 이런 순수한 UI 작업 외에도 다른 작업이 필요할 수 있다.
예를 들어,
useEffect
로 분리해서 다룬다.화면에 그려진 후 실행되는 side effect 처리 함수다.
이는 side effect를 안전하게 처리하고, 필요 시 정리까지 할 수 있게 도와준다. 컴포넌트의 생명주기 동안 특정 작업을 하고 싶을 때 사용한다.
다음은 useEffect의 기본 구조다.
useEffect(() => {
// 실행할 side effect 작업
return () => {
};
}, [의존성])
[의존성]
: 언제 useEffect를 다시 실행할지 기준이 되는 배열return 문
: 컴포넌트가 unmount가 되거나 의존값이 바뀌기 전에 실행되는 정리 작업 함수, clean-up 함수
라고 한다. 다음은 useEffect가 실행되는 시점을 각각 정리해보았다.
컴포넌트가 처음 화면에 나타난 직후 1회 실행된다.
useEffect(() => {
console.log('처음 렌더링 됐을 때만 실행');
}, []);
의존성 배열 []
안의 값이 바뀔 때마다 실행된다.
useEffect(() => {
console.log(`count가 ${count}로 바뀌었어`);
}, [count]);
컴포넌트가 화면에서 사라질 때 return문
으로 정리 작업을 한다.
useEffect(() => {
const timer = setInterval(() => {
console.log('매초 실행');
}, 1000);
return () => {
clearInterval(timer); // 컴포넌트 사라질 때 정리
};
}, []);
다음은 useEffect
를 사용하면서 버그의 원인이 되는 경우와 해결방법이다.
상태 업데이트와 의존성 배열의 조합이 잘못되면 계속 실행되는 무한 루프가 발생한다.
useEffect(() => {
setCount(count + 1); // 의존성에 count가 있을 경우 → 무한 루프
}, [count]);
해결 방법
배열에 필요한 값이 빠지면 useEffect
가 다시 실행이 되지 않는다. -> 버그 발생
useEffect(() => {
fetchData(userId); // userId가 바뀌어도 이펙트가 다시 실행되지 않음
}, []); // userId 없음
해결방법
react-hooks/exhaustive-deps
규칙 사용해서 실수 막기객체와 배열이 의존성 배열에 들어가면, 항상 새로 생성된다.
useEffect(() => {
console.log('실행됨');
}, [[1, 2, 3]]); // 배열은 참조값이 매번 달라져서 매 렌더마다 실행됨
해결 방법
useMemo
로 메모제이션 하기이벤트 리스너, 타이머, 인터벌
을 등록할 경우, 언마운트 시 반드시 제거해줘야 한다. -> 제거하지 않으면, 메모리 누수와 중복 호출 발생 가능
useEffect(() => {
window.addEventListener('resize', onResize);
// 반드시 제거하기
return () => {
window.removeEventListener('resize', onResize);
};
}, []);
useEffect
는 동기 함수만 받을 수 있기 때문에, 비동기 함수는 useEffect
안에서 선언하면 안된다.
다음과 같이 async
함수를 직접 넘기면, async
함수는 자동으로 Promise를 반환
한다. 따라서 useEffect
는 Promise를 clean-up 함수로 오해할 수 있어 에러가 발생할 수 있다.
// 잘못된 코드
useEffect(async () => {
const data = await fetch();
}, []);
해결 방법
// 옳은 코드
useEffect(() => {
const fetchData = async () => {
const res = await fetch('https://localhost/5173');
const result = await res.json();
console.log(result);
};
fetchData(); // 정의한 비동기 함수 실행
}, []);