그림 3-1 컴포넌트의 생명주기
컴포넌트가 화면에 처음 나타날 때를 ‘마운트(Mount)’라고 하고, 화면에서 사라질 때를 ‘언마운트(Unmount)’ 라고 합니다. 이렇게 컴포넌트가 화면에 나타나서 사라질 때까지의 일생을 컴포넌트의 생명주기라고 합니다.
React 앱이 실행되었을 때, 컴포넌트가 호출되면 마운트 상태에 진입합니다. 마운트 상태가 되면 React는 컴포넌트를 계산해 가상 DOM을 생성합니다. 이렇게 가상 DOM으로 렌더링을 진행한 후, 렌더링된 결과물을 실제 DOM에 업데이트하고 마지막으로 실제 DOM에 업데이트된 내용이 화면에 그려지는 페인팅 과정까지를 밟게 됩니다.
API로 데이터를 요청하는 경우, 일반적인 HTTP통신 작업은 비동기로 처리됩니다. 만약 컴포넌트의 본문 안에 HTTP통신 작업이 존재한다면 리렌더링 될 때마다 작업을 다시 요청하게 될 것입니다. API로 요청한 데이터 크기가 클 경우 브라우저의 페인팅 작업이 늦춰져 리소스를 비효율적으로 운영하게 됩니다.
이러한 **부수효과(Side Effects)**들은 주요 화면을 렌더링 하는 데에 직접적으로 영향을 주지 않기 때문에, 주요 렌더링 작업과 부수효과를 구분하여 처리하는 것은 버그를 예방하는 데에도 도움을 줄 뿐만 아니라 좋은 사용자 경험을 제공하는 데에 필수입니다. 이러한 부수효과를 처리하기 위한 React Hook이 바로 useEffect입니다.
<aside> 💡 부수효과(Side Effect) 란?
리액트 공식문서에는 ‘render()
함수는 순수해야 합니다. 즉, 컴포넌트의 state를 변경하지 않고, 호출될 때마다 동일한 결과를 반환해야 하며, 브라우저와 직접적으로 상호작용을 하지 않습니다.’ 라고 언급합니다. 다시 말하면 render()
함수는 주요 화면의 렌더링만을 담당해야 하며, 그 밖에 컴포넌트의 state를 변경하거나 브라우저와 상호작용하여 호출될 때마다 동일한 결과가 반환되지 않는 것들을 부수효과라고 하는 것입니다.
</aside>
기존의 클래스형 컴포넌트 방식에서는 생명주기 메서드를 사용해 부수효과를 처리하였습니다. 예시를 통해 살펴보겠습니다.
import React from "react";
class Section1 extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0,
};
}
componentDidMount() {
console.log("화면에 나타남 🙂");
}
componentDidUpdate() {
console.log("업데이트 발생 😋");
}
componentWillUnmount() {
console.log("사라집니다 ...");
}
render() {
return (
<div>
<h2>클릭한 횟수: {this.state.count}</h2>
<button onClick={() => this.setState({ count: this.state.count + 1 })}>
Click Me💛
</button>
</div>
);
}
}
export default Section1;
componentDidMount
, componentDidUpdate
, componentWillUnmount
는 대표적인 클래스형 컴포넌트의 생명주기 메서드입니다. componentDidMount
는 제일 처음 렌더링 후 돔이 업데이트되는 직후에 실행됩니다. 그리고 버튼을 클릭할 때마다 setState 가 실행되어 리렌더링이 일어나 componentDidUpdate
메서드가 계속 실행되고 있음을 확인할 수 있습니다. componentWillUnmount
는 컴포넌트가 언마운트 되어 DOM에서 제거될 때 실행됩니다.