React 를 학습하면서 Hooks 의 종류별 사용 방법을 학습 중에 있는데 정확한 이해를 위해서는 컴포넌트의 라이프사이클에 대한 학습이 필수적이라 생각했기 때문이다.
학습에 주로 참고한 자료는 리액트를 다루는 기술 책을 참고하였고, 추가적으로 자료를 찾아보면서 적었습니다.
React 에서 사용하는 모든 컴포넌트에는 라이프사이클이 존재한다. 컴포넌트의 수명은 페이지에 렌더링되기 전인 준비과정에서 시작하여 페이지에서 사라질 때 끝이난다.
프로젝트나 학습 중 컴포넌트가 렌더링 할 때, 업데이트 전 후로 어떠한 작업을 진행하는 경우가 있을 것이다. 이럴 때 사용하는 것이 클래스형 컴포넌트에서는 라이프사이클 메서드, 함수 컴포넌트에서는 Hooks (완전하게 동일하게 동작하는 것은 아니고 약간의 차이는 있다.) 이다.
라이프사이클 메서드를 토대로 컴포넌트의 라이프사이클을 알아보려고 한다.
라이프사이클 메서드의 종류는 총 아홉 가지가 있고, 이 메서드들을 활용한 동작이 크게 마운트, 업데이트, 언마운트 가 존재한다. 아래부터 알아볼 내용은 마운트, 업데이트, 언마운트가 어떠한 라이프사이클 메서드를 통해서 동작하며, 각 메서드는 어떠한 동작을 하는지 알아볼 예정이다.
들어가기 전에 하나 알아가면 좋은 것은 어떠한 동작을 하기 전에 발동되는 것은 Will, 동작을 하고 난 이후에 발동되는 것은 Did 라는 접두사가 붙는다는 것을 알고 들어가면 좋다. 또한 이는 클래스형 컴포넌트에서 사용되는 라이프사이클 메서드를 알아보는 것이기 때문에 함수 컴포넌트를 기준으로 생각하면 맞지 않을 수 있다.
DOM 이 생성되고 웹 브라우저상에 나타나는 것을 마운트(mount) 라고 한다. 이 과정에서 호출하는 메서드는 순서대로 construct → getDerivedStateFromProps → render → componentDidMount 이다. 하나씩 어떠한 함수인지 알아보면 다음과 같다.
construct: 컴포넌트를 새로 만들 때마다 호출되는 클래스 생성자 메서드이다.
getDerivedStateFromProps: props 에 있는 값을 state 에 넣을 때 사용하는 메서드이다.
static getDerivedStateFromProps (nextProps, prevState) {
if(nextProps.value !== prevState.value) return { value: nextProps.value };
return null;
}
render: 준비해둔 UI 를 렌더링하는 메서드 이다.
componentDidMount: 컴포넌트가 웹 브라우저상에 나타난 후 호출하는 메서드이다.
말 그대로 컴포넌트가 업데이트 될 때를 말하며 주로 4가지 경우에 업데이트가 이루어진다.
업데이트가 이루어질 때 호출되는 라이프사이클 메서드의 순서는 아래와 같다.
업데이트 발생 → getDerivedStateFromProps → shouldComponentUpdate → [true 인 경우만 이후 메서드 진행] → render → getSnapShotBeforeUpdate → componentDidUpdate
중복된 내용을 제외하고 하나씩 어떠한 함수인지 알아보면 다음과 같다.
shouldComponentUpdate: props 또는 state 를 변경했을 때, 리렌더링을 시작할지 여부를 리턴하는 메서드이다.
getSnapshotBeforeUpdate: render 에서 만들어진 결과물이 브라우저에 실제로 반영되기 직전에 호출되는 메서드
getSnapshotBeforeUpdate(prevProps, prevState) {
if(prevState.array !== this.state.array) {
const { scrollTop, scrollHeight } = this.list;
return { scrollTop, scrollHeight };
}
}
componentDidUpdate(prevProps, prevState, snapshot) { ... }
마운트의 반대 과정으로 컴포넌트를 DOM 에서 제거하는 것을 언마운트(unmount) 라고 한다.
언마운트시에는 componentWillUnmount 메서드만 실행이 된다.
눈치가 빠른 사람이면 9가지의 메서드 중 하나가 비어있다는 것을 알 수 있다.
남은 하나는 componentDidCatch 라는 메서드이다.
componentDidCatch(error, info) {
this.setState({
error: true,
});
// 에러 발생시 원하는 코드 기입을 하면 된다.
console.log({ error, info });
}
import { Component } from 'react';
class ErrorBoundary extends Component {
state = {
error: false,
};
componentDidCatch(error, info) {
this.setState({
error: true,
});
console.log({ error, info });
}
render() {
if(this.state.error) return <div>에러가 발생했습니다!</div>;
return this.props.children;
}
}
export default ErrorBoundary;
// app.js
render() {
...
<ErrorBoundary>
// 컴포넌트들
</ErrorBoundary>
...
}
라이프사이클 메서드는 컴포넌트 상태에 변화가 있을 때 마다 실행되는 메서드이다. 이 메서드들은 서드파티 라이브러리를 사용하거나 DOM 을 직접 건드려야 할 상황에 유용하다.
특히 컴포넌트의 업데이트 성능을 개선할 때 shouldComponentUpdate 메서드가 유용하게 사용된다는 점을 기억해야 한다.
Hooks 란 React v16.8 에 새롭게 도입된 기능으로 함수 컴포넌트에서도 상태 관리를 할 수 있는 useState, 랜더링 직후 작업을 설정하는 useEffect 등의 기능을 제공하여 기존의 함수 컴포넌트에서 할 수 없었던 작업을 할 수 있게 해주는 기능이다.
이 기능이 어떻게 만들어진 것인지 생각을 해본다. 함수에 어떠한 값을 저장하고 유지하는 기능을 우리는 이전에 본 적이 있을 것이다. 바로 클로저이다. React 에서 제공하는 가장 간단한 Hook 인 useState 를 클로저를 활용해서 만들어보면 아래와 같다.
const React = (function () {
let _states = [];
let idx = 0;
function useState(initialValue) {
const state = _states[idx] || initialValue;
const currIdx = idx;
function setState(value) {
_states[currIdx] = value;
}
idx += 1;
return [state, setState];
}
function render(Component) {
return Component();
}
return { useState, render };
})();
렌더링 관련 부분을 제거한 아주 단순한 구조이지만 useState 를 만들었다.
내부적으로 state 들을 저장하기 위한 배열이 존재하고, 인덱스를 통해 배열 내부의 state 에 접근할 수 있도록 하는 클로저를 활용한 구조이다.
리액트가 아주 멋있는 기능을 제공하는 듯 보이지만 (내부를 열어보고 다양한 Hooks 를 보면 실제로는 엄청 멋있는 것이 맞다.) 자바스크립트 기본 요소들을 활용해서 만든다는 것을 알 수 있다.
다음 글에서는 Hooks 에 어떠한 Hook 이 존재하며 어떻게 사용하는지 알아보도록 하겠다.
Hooks 나 컴포넌트를 다루면서 편한 부분만 느끼고 이게 왜 동작하는지에 대한 이해가 부족했는데 나름은 어떠한 구조로 진행되는지를 알게 된 것 같아서 만족한다. 하지만 내부를 깊게 살펴보지는 못한게 아쉬워 다음에는 더 깊게 살펴보는 시간을 가져볼까 한다. 또한 클래스형 컴포넌트와 함수 컴포넌트의 동작에는 차이가 있다는 것을 알게되었는데 Hooks 를 살펴보며 이에 대해서도 더 자세하게 알아보려 한다.