화면에 처음 렌더링되는 것을 Mount
다시 렌더링이 되는 것을 Update
화면에서 사라진 것을 Unmout
useEffect란 컴포넌트가 렌더링 될 때 특정 작업을 실행할 수 있도록 하는 Hook이다.
useEffect는 렌더링 '이후에' 실행이 된다. 즉 컴포넌트가 렌더링이 완료되길 기다린 후에 실행이 된다는 것이다.
useEffect는 인자로 콜백함수를 받고, 그 내부에 원하는 작업을 처리해줄 코드를 작성해주면 된다.
useEffect(() => {
// 작업
})
useEffect(() => {
// 작업
},[value])
Dependency array
[ ]
: 빈 배열이면 컴포넌트가 맨 처음 화면에 렌더링 될 때 실행[값]
: 배열 안의 값이 바뀔 때만 실행예를 들어 타이머를 실행했을 때 더 이상 타이머가 필요 없다면 타이머를 멈추는 작업해줘야 한다. 정리 작업을 처리해 주려면 useEffect
의 return
값으로 함수를 넣어주면 된다. 함수 안에서 원하는 정리 작업을 수행할 수 있다.
useEffect(() => {
// 작업
return () => {
// 작업끝 처리 claen up
},[])
함수를 리턴하면 해당 컴포넌트가 언마운트 될 때 혹은 다음 렌더링시 불려질 useEffect
가 실행되기 이전에 이 함수가 실행이 된다.
여기 업데이트를 누를 때마다 카운트가 하나씩 증가하는 코드가 있다.
import { useState } from "react";
export default function UseEffect() {
const [count, setCount] = useState(1);
const handleCountUpdate = () => {
setCount(count + 1);
};
return (
<div>
<button onClick={handleCountUpdate}>update</button>
<span>count:{count}</span>
</div>
);
}
useEffect를 사용하기 위해 상단에 import를 해주고, 가장 기본적인 useEffect를 만들어준다.
useEffect 안에는 콜백을 하나 만들고, 이 안에 들어 있는 콜백은 컴포넌트가 렌더링 될 때마다 불려지게 된다.
import { useState, useEffect } from "react";
export default function UseEffect() {
const [count, setCount] = useState(1);
const handleCountUpdate = () => {
setCount(count + 1);
};
// 렌더링 될떄마다 매번 실행
useEffect(() => {
console.log('렌더링 🐰')
})
return (
<div>
<button onClick={handleCountUpdate}>update</button>
<span>count:{count}</span>
</div>
);
}
여기서 update 버튼을 누르면 state 가 업데이트 되면서 컴포넌트는 다시 렌더링이 된다.
정리하자면 위 useEffect의 콜백은 컴포넌트가 화면에 렌더링된 직후에 불리는 것이고, state가 변경될 때마다 리렌더링이 되는 것을 볼 수 있다.
그럼, 여기서 코드를 추가하해보자.
import { useState, useEffect } from "react";
export default function UseEffect() {
const [count, setCount] = useState(1);
const [name, setName] = useState("");
const handleCountUpdate = () => {
setCount(count + 1);
};
const handleInputChange = (e) => {
setName(e.target.value);
};
// 렌더링 될떄마다 매번 실행
useEffect(() => {
console.log('렌더링 🐰')
})
return (
<div>
<button onClick={handleCountUpdate}>update</button>
<span>count:{count}</span>
<br></br>
<input type="text" value={name} onChange={handleInputChange} />
<span>name: {name}</span>
</div>
);
}
input에 값을 업데이트할 때마다 handleInputChange 함수가 불리기 때문에 setName이 불리면서 name 스테이트 값이 업데이트 된다. 즉 useEffect안의 콜백이 계속 호출되는 것을 볼 수 있다. 리렌더링이 계속된다 !
seEffect함수가 매번 렌더링 될때마다 실행이 되니까 만약 useEffect 함수 안에서 무거운 작업을 하면 매번 불려지는 것이니 굉장히 비효율 적이다.
그럼 여기서 name 이 실행될 때는 무시하고 count가 업데이트 될 때만 실행시키고 싶으면 ?
useEffect에 두 번째 인자로 count를 넣어준다. 이제 useEffect는 맨 처음 컴포넌트가 화면에 렌더링 되었을 때, 그리고 count 값이 변화될 때만 불리게 될 것이다.
import { useState, useEffect } from "react";
export default function UseEffect() {
const [count, setCount] = useState(1);
const [name, setName] = useState("");
const handleCountUpdate = () => {
setCount(count + 1);
};
const handleInputChange = (e) => {
setName(e.target.value);
};
// 마운팅 + [count]가 변화될 때마다 실행됨
useEffect(() => {
console.log("count 변화 🐥");
}, [count]);
return (
<div>
<button onClick={handleCountUpdate}>update</button>
<span>count:{count}</span>
<br></br>
<input type="text" value={name} onChange={handleInputChange} />
<span>name: {name}</span>
</div>
);
}
이제 반대로 count가 업데이트될 때는 무시하고, name 값이 바뀔 때만 출력을 해보자.
import { useState, useEffect } from "react";
export default function UseEffect() {
const [count, setCount] = useState(1);
const [name, setName] = useState("");
const handleCountUpdate = () => {
setCount(count + 1);
};
const handleInputChange = (e) => {
setName(e.target.value);
};
// 마운팅 + [name]가 변화될때마다 실행됨
useEffect(() => {
console.log("name 변화 🐬");
}, [name]);
return (
<div>
<button onClick={handleCountUpdate}>update</button>
<span>count:{count}</span>
<br></br>
<input type="text" value={name} onChange={handleInputChange} />
<span>name: {name}</span>
</div>
);
}
맨 처음 마운팅 될 때만 useEffect를 실행시켜 주고 싶다면 빈 배열을 넣어준다.
import { useState, useEffect } from "react";
export default function UseEffect() {
const [count, setCount] = useState(1);
const [name, setName] = useState("");
const handleCountUpdate = () => {
setCount(count + 1);
};
const handleInputChange = (e) => {
setName(e.target.value);
};
// 맨 처음 렌더링이 될 때만 실행
useEffect(() => {
console.log("name 변화 🐬");
}, []);
return (
<div>
<button onClick={handleCountUpdate}>update</button>
<span>count:{count}</span>
<br></br>
<input type="text" value={name} onChange={handleInputChange} />
<span>name: {name}</span>
</div>
);
}
먼저 파일 두개를 만들어주자.
1️⃣ 메인 컴포넌트
import { useState } from "react";
import { Timer } from "../../../../src/components/Timer";
export default function useEffect() {
const [showTimer, setShowTimer] = useState(false);
return (
<div>
// showTimer가 true 일때만 Timer 를 보여주자. (토글)
{showTimer && <Timer />}
<button onClick={() => setShowTimer(!showTimer)}>Toggle Timer</button>
</div>
);
}
2️⃣ Timer 컴포넌트
import { useEffect } from "react";
export const Timer = (props) => {
// 화면에 처음 렌더링시 실행, 두번째 인자로 빈 배열 넣어주기
// 맨 처음 마운팅이 되었을때 useEffect 안에 있는 콜백함수가 실행될텐데 setInterval의 인자로 들어가 있는 콜백을 1초마다 한번씩 계속해서 반복해서 부를 거다.
useEffect(() => {
const timer = setInterval(() => {
console.log("타이머 돌아가는 중...");
}, 1000);
}, []);
return (
<div>
<span>타이머를 시작합니다. 콘솔을 보세요!</span>
</div>
);
};
토글타이머를 누르면 타이머 컨포넌트가 마운트가 되고, 토클 타이머를 한번 더 누르면 타이머 컴포넌트가 사라진다. unMount
이때 토글 타이머를 누르면 타이머 컴포넌트가 마운트가 되면서 (처음 렌더링) useeffect가 실행이 되고 1초에 한 번씩 콘솔을 찍는다.
여기서 타이머 컴포넌트가 사라지면서 state의 리렌더링도 끝나는지 콘솔로 확인해 보자.
계속 돌아간다. 왜냐하면 만든 타이머를 정리하지 않았기 때문이다. 즉 종료를 안 해줬다!
Q. 타이머가 언마운트 되었을때 타미머도 멈추게 하려면 ?
useEffect의 return 값으로 함수를 넣어준다. 그리고 그 함수 안에서 정리작업을 해주면 된다.
import { useEffect } from "react";
export const Timer = (props) => {
useEffect(() => {
const timer = setInterval(() => {
console.log("타이머 돌아가는 중");
}, 1000);
// 타이머 컴포넌트가 언마운트 될 때 실행된다. 타이머 정리코드 > clean up !
return () => {
clearInterval(timer);
console.log("타이머가 종료되었습니다.");
};
}, []);
return (
<div>
<span>타이머를 시작합니다. 콘솔을 보세요!</span>
</div>
);
};
지금까지 useEffect에 대해서 다시 알아보았다. 이 과정에서 return 값에 함수를 넣어주면 해당 지정 함수가 종료된 다는 것을 처음 알게 되었다. 그 동안 나는 useEffect를 어떻게 쓴건지..🤔 리팩토링 과정에서 유용하게 쓰일 것 같으니 저장! 그렇다고 useEffect를 남발하면 안되고, useEffect 안에서 컴포넌트의 정보를 사용하는것을 최소화 해야한다. 왜냐하면 useEffect안에서 컴포넌트 내부에 있는 값을 많이 가져다 사용할수록 렌더링 스코프에 갇힌 변수들이 많아진다는것이고 그 말은 결국, 디버깅하기 힘든 이슈가 발생할 가능성이 높아진다는것을 의미하기 때문이다.