컴포넌트도 생명 주기라는 것이 존재한다.
이 링크에 접속하면 직관적으로 컴포넌트의 생명주기에 대해 이해할 수있다.
예를 들어서 쇼핑몰 페이지에 접속해서 제품에 대한 상세 설명 페이지를 접속한다고 가정 했을때,
이러한 과정들을 갖는 생명주기를 갖는다.
컴포넌트의 이러한 생명주기들을 잘 파악하고 있다면, 컴포넌트의 생명주기 사이사이에 간섭을 해줄수 있다.
즉
등을 작성해줌으로써 컴포넌트들을 렌더링 될때 동시에 특정 작업을 할 수 있다. (갈고리를 걸어놓는다)
Hook에 대한 설명은 글에 작성 되어 있듯이 함수형 컴포넌트에서 클래스형 컴포넌트의 기능들을 사용할 수 있는 것이다.
useEffect는 함수형 컴포넌트에서 컴포넌트의 생명주기에 간섭할수 있도록 하게 해주는 Hook이다.
// 클래스형 컴포넌트에서 Lifecycle Hook 사용하는 법
class Detail2 extends React.Component {
componentDidMount(){
//Detail2 컴포넌트가 로드되고나서 실행할 코드
}
componentDidUpdate(){
//Detail2 컴포넌트가 업데이트 되고나서 실행할 코드
}
componentWillUnmount(){
//Detail2 컴포넌트가 삭제되기전에 실행할 코드
}
// useEffect()를 사용하는 법
import {useState, useEffect} from 'react';
function Detail(){
useEffect(()=>{
//여기적은 코드는 컴포넌트 mount, update 될때 마다 실행됨
console.log('안녕')
});
}
❓ 근데 왜 안녕이 2번이나 출력이 될까 ❓
원래 리액트 상에서 디버깅용으로 useEffect 안에 있는 코드는 2번이 실행 된다.
실제로 사이트상에서 발행을 할때는 1번 동작하고, 저기 console 창에서만 임의적으로 2번이 실행 되는 것이다. (신경 쓸 필요 없음!)
1번만 출력되게 하려면 index.js 를 가서 React.strictMode 를 제거해주어야한다.
useEffect(() => {
console.log('안녕');
})
let [count, setCount] = useState(0)
{count}
<button onClick={() => setCount(count+1)}>버튼</button>
버튼을 누를때마다 (재 렌더링 될때마다) useEffect 안에 있는 코드가 실행되도록 했다.
"안녕"의 출력 횟수가 늘어나는걸 확인할 수 있다.
❗️ 하지만 저 console.log('안녕'); 은 useEffect 바깥에서 작성을 해도 위와 똑같이 출력된다. 왜일까? ❗️
예를 들어서 반복문을 돌려서 1부터 10000까지 출력하는 코드가 있다고 가정해보자
useEffect(() => {
for(let i =0; i <= 10000; i++){
console.log(i);
}
})
이렇게 코드를 작성하면 일단 HTML 을 렌더링 한 후에 저 반복문을 실행시켜주기 때문에, HTML 의 렌더링 속도를 높일수 있다.
HTML 이 렌더링 될때마다 동작하는 특정 작업을 Side Effect라고하는데, useEffect라는것도 그에 맞게 작명을 해서 렌더링 될때 처리되어야할 작업들을 비동기로 처리해주는 것이다.
컴포넌트의 핵심 기능은 HTML 렌더링이기 때문에, 그거 외에 다른 기능들을 useEffect 안에 넣어주면 HTML 렌더링 속도를 향상시킬 수 있다. (타이머, 반복연산, 서버에서 데이터가져오기 등등)
let [alert, setAlert] = useState(true);
useEffect(() => {
setTimeout(() => {
setAlert(false);
}, 2000);
});
{alert === true ? (
<div className="alert alert-warning">2초 이내로 구매시 할인</div>
) : null}
alert 와 setAlert 라는 state를 설정하고 alert가 true라면 저 div창을 띄운다.
그후에 useEffect()를 사용해서 2초후에 false로 바뀌게 하면서 그 창을 없애도록 하였다.
📌 useEffect의 형태는 원래 useEffect(callback, dependencyArray) 이다.
📌 그리고 useEffect는 기본적으로 매 렌더링마다 실행된다. 만약에 dependency가 없었다면 컴포넌트가 마운트되고, 업데이트될때마다 useEffect()가 실행 됐을 것이다.
위의 저 코드에서 사실 useEffect() 에서 두번째 인자로 [] 를 넣어주어야한다.
let [count, setCount] = useState(0);
useEffect(() => {
setTimeout(() => {
console.log('Hi');
}, 1000);
}, [count]);
{count}
<button onClick={() => setCount(count + 1)}>버튼</button>
버튼을 눌러서 저 count state를 변경할때마다 콘솔창을 띄우도록 해보았다.
버튼을 누를때마다 useEffect 안에 있는 콘솔창이 실행되는것을 확인 할 수있다.
만약에 dependency가 없다면 마운트되거나 업데이트 될때마다 실행되지만, dependency를 넣어주면
마운트시 1회만 useEffect 안의 내용들을 실행하고 그 후에는 실행해주지 않는다.
useEffect(() => {
setTimeout(() => {
setAlert(false);
console.log('Hi');
}, 1000);
}, []);
{alert === true ? (
<div className="alert alert-warning">2초 이내로 구매시 할인</div>
) : null}
만약에 위처럼 일정시간이 지나면 div를 숨기는 useEffect를 실행한다고 했을때, 만약에 []가 없었다면 마운트 될때마다 실행이 되겠지만, [] 를 추가해줌으로써 마운트시에 1회만 실행되도록 한다.
useEffect(() => {
setTimeout(() => {
setAlert(false);
console.log('Hi');
}, 1000);
return () => {
console.log('안녕하세요');
}
}, []);
렌더링해보면 return문 안에 있는 안녕하세요가 먼저 나온후 그 후에 Hi 가 출력이 되는 것을 확인할 수 있다.
이 useEffect가 실행되기전에 먼저 실행되는 return문을 Clean up function 이라고 하는데, 예를 들어서 위의 코드의 useEffect안에 dependency를 작성하지 않았다고 가정해보자. 그렇다면 렌더링 될때마다 setTimeout 함수가 실행될텐데 나중가면 타이머가 여러개 생겨서 버그를 일으킬 수 있다. 이럴때 Clean up function 을 사용해서 타이머 함수를 사용하기 전에 기존 타이머들을 다 제거해줄수가 있다.
useEffect(()=>{
let a = setTimeout(()=>{ setAlert(false) }, 2000)
return ()=>{
clearTimeout(a)
}
}, [])
만약 위의 코드라면 기존에 있던 타이머함수 a를 지우고 그 후에 타이머함수를 새로 실행시켜준다는 의미이다.
Clean up function은 타이머함수제거, socket 연결 요청 제거, ajax 요청 중단 등 에 사용된다.
또한 컴포넌트를 언마운트(컴포넌트를 벗어날때) 할때도 Clean up function이 실행 된다.