시작하기 전에
리액트 라이프 사이클에 대해 정확하게 알고 있지 않기에 이 포스트를 작성하기로 했다.이 링크를 참고하면 좀 더 자세하게 LifeCyle을 이해할 수 있다. 앞으로 LifeCylcle의 업데이트된 사항을 위 링크에서 확인해 보고 이전과 비교하면 더 좋을 것이다.
1. 리액트에 라이프 사이클 이란 ?
리액트는 컴포넌트 기반의 View를 중심으로 한 라이브러리 언어이다. 그렇다보니 각각의 컴포넌트에는 라이프 사이클 즉, 컴포넌트의 수명 주기가 존재한다. 컴포넌트의 수명은 보통 페이지에서 렌더링 되기 전 준비 과정에서 시작해
페이지에서 사라질때 끝이난다.
위의 이미지는 리액트의 라이프 사이클을 나타낸 화면이다. 이제 이 각각의 메서드들이 어떤 역할을 하는 지 알아보겠다.
2. 라이프사이클의 분류
라이프 사이클은 위 그림과 같이 총 9개가 존재한다.
크게 세가지 유형으로 나눌 수 있는데 생성 될때, 업데이트 할 때,제거할 때이다. 이를 리액트에서는
마운트, 업데이트, 언마운트라고 한다.
여기서 마운트 DOM이 생성되고 웹 브라우저 상에서 나타나는 것을 뜻하고, 반대로 언마운트는 DOM에서 제거되는 것을 뜻한다.
주의하여 볼 것은 업데이트 부분인데, 업데이트는 다음과 같은 4가지 상황에서 발생한다.
1. props가 바뀔 때
2. state가 바뀔 때
3. 부모 컴포넌트가 리렌더링 될 때
4. this.forceUpdate로 강제로 렌더링을 트리거할 때
그럼 이제 본격적으로 각각의 라이프 사이클이 무엇인지 어떻게 Class와 Hooks를 활용해 함수형 컴포넌트에서 사용하는지 알아보자.
3.라이프 사이클에 메서드 살펴보기
1. constructor
자바 같은 언어를 써봤다면 들어봤을 만한 constructor(생성자)이다. 이것은 자바와 마찬가지로 컴포넌트를 만들 떄 처음으로 실행된다. 이 메서드에서는 초기 state를 정할 수 있다.
// Class
class Expample extends React.Component {
constructor(props) {
super(props);
this.state = {count: 0};
}
// Hooks
const Example = () => {
const [count, setCount] = useState(0);
}
클래스형에서는 초기 state를 설정할 때 constructor를 사용해야한다. 하지만 Hook에서는 useState Hook을 사용하면
초기 상태를 쉽게 설정해줄 수 있다.
2. getDerivedStateFromProps
- 추후 리액트 측에서 엡데이트 할 시 보충 필요
이 메서드는 리액트 16.3버전 이후에 생긴 메서드이다. props로 받아온 값을 state에 동기화시키는 용도로 사용하며,
컴포넌트가 마운트될 때와 업데이트 될 떄 호출된다.
이 메서드는 다음과 같은 예에서 사용된다고 한다.
// Class
class Example extends React.Component {
static getDerivedStateFromProps(nextProps, prevState) {
if (nextProps.value !== prevState.value) {
return { value: nextProps.value }
}
return null
}
}
3. shouldComponentUpdate
이 메서드는 props나 state를 변경할때, 리렌더링을 할지 말지를 결정하는 메서드이다. 이 메서드에서는 반드시
true or false를 반환해주어야 한다. 이 메서드에서는 오직 성는 최적화 만을 위한 것이므로 렌더링 목적을 방지하
는 목적으로 사용된다면 버그가 발생할 수 있다.
// Class
class Example extends React.Component {
shouldComponentUpdate(nextProps) {
return nextProps.value !== this.props.value
}
}
// Hooks
const Example = React.memo(() => {
...
},
(prevProps, nextProps) => {
return nextProps.value === prevProps.value
}
)
클래스형도 보통은 PureComponent를 추천한다고 하고 Hooks에서도 props는 React.memo, state는 useMemo를 활욯하면 렌더링 성능을 개선할 수 있다.
4. render
render는 가장 기초적인 메서드로써 가장 중요한 메서드이기도 하다. 컴포넌트를 렌더링할 때 필요한 메서드로 유명한
필수 메서드이다. 컴포넌트에서는 render를 안쓰고도 컴포넌트를 렌더링 할 수 있다.
// Class
class App extends React.Component {
render() {
return (
<div>
<Example />
</div>
)
}
}
// Hooks
const Example = () => {
return <div>컴포넌트 사항</div>
}
5. getSnapshotBeforeUpdate
이 메서드는 render에서 만들어진 결과가 브라우저에 실제로 반영되기 직전에 호출된다. 공식문서를 참고해보면
메서드에 대한 사용 예는 흔하지 않지만, 채팅 화면처럼 스크롤 위치를 따로 처리하는 작업에 필요한 UI 등을
생각해 볼수 있다.
class Example extends React.Component {
getSnapshotBeforeUpdate(prevProps, prevState) {
if (prevProps.list.length < this.props.list.length) {
const list = this.listRef.current
return list.scrollHeight - list.scrollTop
}
return null
}
}
함수형에서는 아직 이 기능을 대체할만한 Hook은 없다고 본다..
이 내용을 읽고 이해되지 않는다면 velopert님의 lifeCycle 페이지의 getSnapshotBeforeUpdate 부분을 참조하는 것도
도움이 된다.
6. componentDidMount
이 메서드는 컴포넌트를 만들고 첫 렌더링을 마친 후에 코드가 실행된다. 함수형 Hooks에서는 useEffect를 활용해
다음의 기능을 구현할 수 있다.
// Class
class Example extends React.Component {
componentDidMount() {
...
}
}
// Hooks
const Example = () => {
useEffect(() => {
...
}, []);
}
여기서 useEffect의 [] 의존성 배열을 비워야 똑같은 매서드로 구현할 수 있다.
useEffect에 익숙하지 않다면 리액트 공식문서 - useEffect를 참조하면 된다.
7. componentDidUpdate
이것은 리렌더링을 완료한 후 실행된다. 업데이트가 끝난 직후로, DOM관련 처리를 해도 무방하다.
// Class
class Example extends React.Component {
componentDidUpdate(prevProps, prevState) {
...
}
}
// Hooks
const Example = () => {
useEffect(() => {
...
});
}
8. componentDidUpdate
이 메서드는 컴포넌트 DOM에서 제거할 때 실행된다. componentDidMount에서 등록한 이벤트가 있다면 이곳에서 제거 작업을 해야한다. 함수형 컴포넌트에서는 useEffect CleanUp 함수를 통해 해당 메서드를 구현할 수 있다.
// Class
class Example extends React.Component {
coomponentWillUnmount() {
...
}
}
// Hooks
const Example = () => {
useEffect(() => {
return () => {
...
}
}, []);
}
9.componentDidCatch
마지막으로 componentDidCatch라는 매서드가 존재한다. 이 메서드로는 컴포넌트 렌더링 도중에 에러가 발생하면
애플리케이션이 멈추지않고 오류 UI를 보여줄 수 있게 해준다.
// Class
class Example extends React.Component {
componentDidCatch(error, info) {
console.log('에러가 발생했습니다.')
}
}
예제)
라이프사이클 메서드를 실행할 때마다 콘솔 디버거에 기록하면서 부모 컴포넌트에서 props 색상을 받아 버튼을 누르면 statenumber 값을 1씩 더하기
- getDerivedStateFromProps : 부모에게서 받은 color값을 state에 동기화
- getSnapshotBeforeUpdate : DOM에 변화가 일어나기 직전 색상 속성을 snapshot+ 값으로 반환
- componentDidUpdate : snapshot 반환값을 조회하여 사용
- shouldComponentUpdate : state.number 값 사용
LifeCycleSample.js
import React, { Component } from 'react';
class LifeCycle extends Component {
state = {
number: 0,
color: null,
}
myRef = null; // ref 설정 부분
// 마운트 : 1, 생성자
constructor(props) {
super(props);
console.log('constructor');
}
// 마운트: props에 있는 값을 state에 넣을 때 사용
static getDerivedStateFromProps(nextProps, prevState) {
console.log('getDerivedStateFromProps');
if (nextProps.color !== prevState.color) {
return { color: nextProps.color };
}
return null;
}
// 마운트 : 컴포넌트가 웹 브라우저 상에 나타난 후 호출
componentDidMount() {
console.log('componentDidMount');
}
// 업데이트 : 컴포넌트가 리렌더링 해야할지 여부 결정
shouldComponentUpdate(nextProps, nextState) {
console.log('shouldComponentUpdate' , nextProps, nextState);
// 숫자의 마지막 자리가 4면 리렌더링 되지않음
return nextState.number % 10 !== 4;
}
// 언마운트 : 컴포넌트가 DOM 상에서 제거될 때 호출
componentWillUnmount() {
console.log("componentWillUnmount");
}
handleClick = () => {
this.setState({
number: this.state.number + 1
});
}
// 업데이트 : 컴포넌트 변화에 DOM에 반영하기 바로 직전에 호출
getSnapshotBeforeUpdate(prevProps, prevState) {
console.log('getSnapshotBeforeUpdate');
if (prevProps.color !== this.props.color) {
return this.myRef.style.color;
}
return null;
}
// 업데이트 : 컴포넌트의 업데이트
componentDidUpdate(prevProps, prevState, snapshot) {
console.log('componentDidUpdate', prevProps, prevState);
if (snapshot) {
console.log('업데이트 되기 직전 색상:' , snapshot);
}
}
// 마운트, 업데이트 : 컴포넌트를 렌더링 ,리렌더링
render() {
console.log('render');
const style = {
color: this.props.color
};
return (
<div>
<h1 style={style} ref={ref => this.myRef=ref}>
{this.state.number}
</h1>
<p>color: {this.state.color}</p>
<button onClick={this.handleClick}>
더하기
</button>
</div>
);
}
}
export default LifeCycle;
ErrorBoundary.js
에러가 발생하면 componentDidCatch 메서드가 호출되고, 이 메서드에는 this.state.error 값을 true로 업데이트해 준다.
그리고 render 함수는 this.state.error 값이 true라면 에러가 발생하였음을 알려준다.
import React, { 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
import React, { Component } from 'react';
import LifeCycle from './LifeCycleSample';
import ErrorBoundary from './ErrorBoundary';
// 랜덤 색상 생성
function getRandomColor() {
return '#' + Math.floor(Math.random() * 1677215).toString(16);
}
class App extends Component {
state = {
color: '#000000'
}
handleClick = () => {
this.setState({
color: getRandomColor()
});
}
render() {
return (
<div>
<buttn onClick={this.handleClick}>랜덤 색상</buttn>
<ErrorBoundary>
<LifeCycle color={this.state.color}/>
</ErrorBoundary>
</div>
)
}
}
export default App;
참고
'프론트 엔드 > React' 카테고리의 다른 글
[React] Stateful / Stateless (0) | 2021.10.11 |
---|---|
[React] React Hooks이란? (0) | 2021.10.10 |
[React] 컴포넌트 성능 개선 React.memo() (0) | 2021.10.09 |