componentDidMount가 컴포넌트에 ‘탄생’의 순간을 알려주었다면, 이제는 그 반대의 순간, 즉 ‘죽음’의 순간을 알려줄 차례였다. 맷이 처음 제기했던 <NewsNotifier>의 메모리 누수 문제는 아직 절반만 해결된 상태였다.
“타이머를 시작하는 법은 알았습니다. 하지만 컴포넌트가 화면에서 사라질 때, 이 타이머를 어떻게 깨끗하게 정리하죠?”
이 질문은 모든 종류의 ‘구독(Subscription)’ 형태의 작업에 해당하는 문제였다.
setInterval이나setTimeout같은 타이머.- 전역
window객체에 추가한 이벤트 리스너 (예:window.addEventListener('resize', ...)). - 실시간 데이터를 주고받는 웹소켓(WebSocket) 연결.
이런 작업들은 한번 시작되면, 누군가 명시적으로 ‘중단’시키지 않는 한 영원히 메모리에 남아 시스템 리소스를 좀먹는 유령이 될 수 있었다.
이 문제를 해결하기 위해, 조던과 팀은 생명주기 목록에서 componentDidMount의 완벽한 짝을 찾아냈다.
componentWillUnmount()
이름이 모든 것을 말해주었다.
- component: 컴포넌트 인스턴스에 속한 메서드.
- Will: 미래형 시제. 어떤 일이 ‘일어나기 직전’을 의미한다.
- Unmount: ‘언마운트하다’, 즉 컴포넌트가 DOM에서 제거되는 과정.
종합하면, componentWillUnmount는 “컴포넌트가 실제 DOM에서 제거되기 바로 직전에, 리액트가 마지막으로 호출해주는 정리의 기회”였다.
개발자는 이 메서드 안에서, 컴포넌트가 componentDidMount나 다른 곳에서 벌여놓았던 모든 일들을 깨끗하게 치워야 할 책임이 있었다.
맷은 자신의 <NewsNotifier> 컴포넌트에 이 새로운 생명주기 메서드를 추가하여 코드를 완성했다.
var NewsNotifier = React.createClass({
getInitialState: function() { /* ... */ },
componentDidMount: function() {
// 컴포넌트 인스턴스에 timerID를 저장해둔다.
this.timerID = setInterval(this.checkForNotifications, 5000);
},
// 컴포넌트가 DOM에서 제거되기 직전에 호출된다.
componentWillUnmount: function() {
console.log('NewsNotifier가 곧 사라집니다. 타이머를 정리합니다.');
// componentDidMount에서 시작했던 타이머를 깨끗하게 정리한다.
clearInterval(this.timerID);
},
checkForNotifications: function() { /* ... */ },
render: function() { /* ... */ }
});
이제 <NewsNotifier>는 완벽하게 독립적이고 안전한 부품이 되었다.
- 태어날 때(
componentDidMount): 스스로 타이머를 시작한다. - 살아있는 동안(
render,setState): 스스로의 상태를 관리하며 화면을 업데이트한다. - 죽을 때(
componentWillUnmount): 자신이 시작했던 타이머를 스스로 정리하고 떠난다.
이 컴포넌트를 사용하는 부모는 <NewsNotifier>의 복잡한 내부 동작에 대해 아무것도 알 필요가 없었다. 그저 필요할 때 화면에 렌더링하고, 필요 없어지면 화면에서 제거하기만 하면 되었다. 뒷정리는 전적으로 컴포넌트 자신의 책임이었다.
componentWillUnmount의 도입은 리액트 컴포넌트의 캡슐화를 완성하는 마지막 화룡점정이었다. 컴포넌트는 이제 자신의 탄생뿐만 아니라, 자신의 죽음까지도 스스로 책임지는 완전한 생명체로 거듭났다.
이로써 개발자들은 애플리케이션 전체의 상태를 걱정하는 대신, 각 컴포넌트의 짧은 생애 동안의 책임과 역할에만 집중할 수 있게 되었다. 리액트는 복잡한 시간의 흐름 속에서 발생하는 수많은 문제를, ‘생명주기’라는 우아한 개념 안에 완벽하게 정리해내고 있었다.


