우리는 오늘날 리액트를 사용하면서, 함수 컴포넌트를 대부분 사용하기 때문에 라이프 사이클에 대해서 잘 알지 못한채 넘어가고는 한다. 오늘은 클래스 컴포넌트의 라이프 사이클에 대해 알아보면서, 컴포넌트가 브라우저 상에 나타나고, 업데이트 되고, 사라지게 될 때 호출되는 메서드들에 알아보고자 한다.
리액트는 컴포넌트 기반의 View를 중심으로 한 자바스크립트 라이브러리이다. 즉, MVC(Model - View - Controller) 패턴이 아닌 오직 사용자에게 보여지는 View만 신경 쓰는 라이브러리라고 할 수 있다. 여기서 사용자에게 특정 부분이 어떻게 View 될 지 정하는 선언체를 컴포넌트(Component)라고 한다.
각각의 컴포넌트에는 컴포넌트의 수명 주기인 라이프사이클이 존재한다. 컴포넌트의 수명은 평균적으로 페이지에서 렌더링 되기 전인 준비 과정에서 시작하여 페이지에서 사라질 때 끝이 난다. 여기서 렌더링이란, 사용자 화면에 View를 보여주는 것을 뜻한다.
라이프사이클은 위 사진처럼 총 9개가 존재하지만, 크게 생성될 때, 업데이트 될 때, 제거 될 때의 3가지 유형으로 구분지을 수 있다. 이를 다른 말로 마운트(Mount), 업데이트(Update), 언마운트(Unmount)라고 한다. 리액트의 업데이트는 props가 변경될 때, state가 변경될 때, 부모 컴포넌트가 리렌더링 될 때, this.forceUpdate 함수가 사용될 때 발생한다.
그럼 지금부터, 각각의 라이프사이클에 대해 알아보자.
마운트 시 발생하는 라이프사이클들은 다음과 같다.
constructor
getDerivedStateFromProps
render
componentDidMount
constructor(props) {
super(props);
console.log("constructor");
}
constructor
는 컴포넌트의 생성자 메서드로, 컴포넌트가 생성될 때 가장 먼저 실행되는 메서드이다.
// Class
constructor(props) {
super(props);
this.state = { isActive : false }
}
// Hooks
const [isActive, setIsActive] = useState(false);
클래스 컴포넌트에서는 state의 초기값을 설정해 주려면 constructor 메서드를 사용해야 한다. 함수 컴포넌트에서는 useState Hook을 활용해 간단하게 초기값을 설정할 수 있다.
getDerivedStateFromProps
는 props로 받아온 값을 state에 넣을 때 사용하는 메서드이다. 다른 라이프사이클과는 다르게 static을 우선적으로 작성하고 사용해야 하고, this를 참조할 수 없다.
static getDerivedStateFromProps(nextProps, prevState) {
if (nextProps.value !== prevState.value) {
return { value: nextProps.value }
}
return null;
}
특정 객체를 반환하게 되면, 해당 값이 컴포넌트의 state로 설정이 되고 null을 반환하게 되면 아무 일도 발생하지 않게 된다.
function ScrollView({row}) {
const [isScrollingDown, setIsScrollingDown] = useState(false);
const [prevRow, setPrevRow] = useState(null);
if (row !== prevRow) {
// 마지막 렌더링 이후 행이 변경되었습니다. isScrollingDown을 업데이트합니다.
setIsScrollingDown(prevRow !== null && row > prevRow);
setPrevRow(row);
}
return `Scrolling down: ${isScrollingDown}`;
}
리액트의 공식문서에서는 getDerivedStateFromProps를 Hook에서 구현하는 방식을 위 코드와 같이 설명하고 있다.
render
는 컴포넌트를 렌더링하는 메서드이다. 클래스 컴포넌트에서는 컴포넌트를 렌더링할 때 필요한 필수 메서드이지만, 함수 컴포넌트에서는 render
메서드 없이도 컴포넌트 렌더링이 가능하다.
render() {
return <div>Hello, React!</div>
}
componentDidMount
는 컴포넌트의 첫 렌더링이 종료되면 호출되는 메서드로, 외부 라이브러리를 연동해야 할 때나, axios, fetch 등을 통하여 ajax 요청을 할 때, DOM 의 속성을 읽거나 직접 변경하는 작업을 진행할 때 사용한다. 함수 컴포넌트에서는 useEffect Hook을 활용하여 같은 기능을 구현할 수 있지만, dependency array를 비워둬야 한다.
// Class
componentDidMount() {
fetch(...)
}
// Hooks
useEffect(() => {
fetch(...)
}, []);
컴포넌트가 업데이트 될 시에 발생하는 라이프사이클들은 다음과 같다.
getDerivedStateFromProps
shouldComponentUpdate
render
getSnapshotBeforeUpdate
componentDidUpdate
getDerivedStateFromProps
메서드는 컴포넌트가 마운트 될 때도 호출되지만, 컴포넌트의 state나 props가 변경되었을 때에도 호출된다.
shouldComponentUpdate
메서드는 컴포넌트의 리렌더링 여부를 결정하는 메서드이다. 이 메서드에서는 반드시 true나 false를 반환해야 하며, 성능 최적화를 위해 사용한다.
// Class
shouldComponentUpdate(nextProps) {
return nextProps.value !== this.props.value
}
// Hooks
React.memo(() => {
...
},
(prevProps, nextProps) => {
return nextProps.value === prevProps.value
}
)
함수 컴포넌트에서는 React.memo()
를 활용하여 같은 기능을 구현할 수 있으며, state의 경우에는 useMemo Hook을 통해 렌더링 성능 최적화가 가능하다.
render
메서드 역시 컴포넌트의 업데이트가 발생하면 호출된다.
getSnapShotBeforeUpdate
메서드는 렌더링 결과가 실제 DOM에 반영되기 이전에 호출되는 메서드로, 이름 그대로 업데이트 되기 직전에 props나 state에 대한 스냅샷을 확보하는게 목적이다. getSnapshotBeforeUpdate의 반환값은 componentDidUpdate의 세번째 인자로 들어갈 수 있으며, componentDidUpdate에서는 이를 통해 DOM의 상탯값 변화를 알 수 있다.
getSnapshotBeforeUpdate(prevProps, prevState) {
if (prevProps.color !== this.props.color) {
return this.myRef.style.color;
}
return null;
}
componentDidUpdate
메서드는 리렌더링이 완료된 후에 실행되는 메서드로, DOM 관련 처리를 할 수 있으며 이전에 설명했던 getSnapshotBeforeUpdate에서 반환한 값을 조회할 수 있다. 함수 컴포넌트에서는 componentDidMount와 마찬가지로 useEffect를 통해 같은 기능을 구현할 수 있다.
componentDidUpdate(prevProps, prevState, snapshot) {
console.log("componentDidUpdate", prevProps, prevState);
if (snapshot) {
console.log("업데이트 되기 직전 색상: ", snapshot);
}
}
언마운트에 관련된 라이프사이클 메서드는 componentWillUnmount 하나밖에 존재하지 않는다.
componentWillUnmount
메서드는 컴포넌트가 화면에서 사라지기 직전에 호출되며, componentDidMount에서 등록한 이벤트가 있다면 여기서 제거해야 한다. 만약에 setTimeout을 사용했다면 이 부분에서 clearTimeout 을 통하여 제거한다. 함수 컴포넌트에서는 useEffect CleanUp 함수를 통해 같은 기능을 구현할 수 있다.
// Class
componentWillUnmount() {
...
}
// Hooks
useEffect(() => {
return () => {
...
}
}, []);
위의 라이프사이클 사진에는 존재하지 않지만, componentDidCatch
라는 메서드 역시 존재한다. 이 메서드는 렌더링 도중에 발생한 오류에 대해 어플리케이션을 멈추지 않고 오류 UI를 유저에게 보여줄 수 있게 해줄 때 사용한다.
componentDidCatch(error, info) {
console.log('에러가 발생했습니다.')
}
이렇게 모든 라이프사이클에 대해 알아보았다. 사실 나는 리액트를 입문부터 함수 컴포넌트로 입문을 해서 라이프사이클에 대해 잘 알지 못했는데, 이번 기회를 통해서 알고 넘어갈 수 있게 된 것 같다. Hooks로도 라이프사이클을 구현할 수 있으니 개념에 대해 정확히 알고 정확한 Hook을 사용할 수 있도록 연습해야겠다!