안녕하세요, 코린이의 코딩 학습기 채니 입니다.
[리액트를 다루는 기술]의 책을 참고하여 포스팅한 개인 공부 내용입니다.
컴포넌트의 라이프사이클 메소드
모든 리액트 컴포넌트에는 라이프사이클 (생명 주기)이 존재합니다.
프로젝트 진행 시 컴포넌트를 처음으로 렌더링할 때 어떠한 작업을 처리하거나, 업데이트 전 후로 어떠한 작업을 처리해야 할 수도, 불필요한 업데이트를 방지해야 할 수도 있습니다.
이 때 라이프사이클 메소드를 이용하며, 클래스형 컴포넌트에서만 사용할 수 있습니다.
(함수형은 Hooks 기능을 이용해 비슷하게 처리)
라이프사이클 메소드의 이해
- Will 접두사 : 어떤 작업을 작동하기 전에 실행되는 메소드
- Did 접두사 : 어떤 작업을 작동한 후에 실행되는 메소드
이러한 총 9가지의 메소드들을 컴포넌트 클래스에서 덮어 써 선언하여 사용합니다.
라이플 사이클은 총 3가지로 나뉩니다.
- 마운트
- 업데이트
- 언마운트
마운트
- DOM이 생성되고 웹 브라우저상에 나타나는 것으로, 호출 메소드는 아래와 같습니다.
- constructor : 컴포넌트를 새로 만들 때마다 호출되는 클래스 생성자 메소드
- getDerivedStateFromProps : props에 있는 값을 state에 넣을 때 사용하는 메소드
- render : 준비한 UI를 렌더링하는 메소드
- componentDidMount : 컴포넌트가 웹 브라우저상에 나타난 후 호출하는 메소드
업데이트
- 컴포넌트는 총 네 가지 경우에 업데이트를 실행합니다.
- props가 바뀔 때 (부모 컴포넌트에서 넘겨주는 props가 변경)
- state가 바뀔 때 (자신이 들고 있는 state가 setState를 통해 업데이트)
- 부모 컴포넌트가 리렌더링될 때 (자신에게 할당된 props가 바뀌지 않아도, 자신의 state가 바뀌지 않아도 부모 컴포넌트가 리렌더링되면 자식 컴포넌트도 리렌더링)
- this.forceUpdate로 강제로 렌더링을 트리거 할 때
- getDerivedStateFromProps : 마운트 과정에서도 호출되며, 업데이트가 시작하기 전에도 호출되는 메소드
props의 변화에 따라 state 값에도 변화를 주고 싶을 때 사용 - shouldComponentUpdate : 컴포넌트가 리렌더링을 해야 할지 말아야 할지를 결정하는 메소드
true 혹은 false를 반환하며, true 반환 시 다음 메소드를 계속 실행하고 false 반환 시 작업을 중지(컴포넌트 리렌더링 X)
만일, this.forceUpdate() 함수 호출 시 이 과정을 생략하고 render 함수 호출 - render : 컴포넌트 리렌더링
- getSnapshotBeforeUpdate : 컴포넌트 변화를 DOM에 반영하기 바로 직전에 호출하는 메소드
- componentDidUpdate : 컴포넌트의 업데이트 작업이 끝난 후 호출하는 메소드
언마운트
- 마운트의 반대 과정, 컴포넌트를 DOM에서 제거하는 것
- componentWillUnmount : 컴포넌트가 웹 브라우저상에서 사라지기 전에 호출하는 메소드
라이프사이클 메소드 살펴보기
render() 함수
render() { ... }
컴포넌트의 모양새를 정의하며, 가장 중요한 메소드로 유일한 필수 메소드입니다.
render 함수 안에서 this.props와 this.state에 접근 가능하고, 리액트 요소를 반환합니다. (div 같은 태그, 컴포넌트 혹은 null 값 / false로 아무것도 보여주지 않음)
해당 함수 안에서는 이벤트 설정이 아닌 곳에서 setState 사용 혹은 브라우저의 DOM에 접근하면 안됩니다.
(DOM 정보를 가져오거나 state 변화를 줄 때는 componentDidMount에 처리!)
constructor 메소드
constructor(props) { ... }
컴포넌트의 생성자 메소드로 컴포넌트를 만들 때 처음으로 실행되며, 초기 state를 정할 수 있습니다.
getDerivedStateFromProps 메소드
static getDerviedStateFromProps(nextProps, prevState) {
if(nextProps.value !== prevState.value) { // 조건에 따라 특정 값 동기화
return { value: nextProps.value };
}
return null; // state를 변경할 필요가 없다면 null 반환
}
props로 받아 온 값을 state에 동기화시키는 용도로 사용하며, 컴포넌트가 마운트될 때와 업데이트될 때 호출됩니다.
componentDidMount 메소드
componentDidMount() { ... }
컴포넌트를 만들고, 첫 렌더링을 다 마친 후 실행됩니다.
해당 메소드 안에서 다른 자바스크립트 라이브러리 또는 프레임워크의 함수를 호출, 이벤트 등록, setTimeout, setInterval, 네트워크 요청 같은 비동기 작업을 처리합니다.
shouldComponentUpdate 메소드
shouldComponentUpdate(nextProps, nextState) { ... }
props 또는 state를 변경했을 때, 리렌더링을 시작할지 여부를 지정하는 메소드입니다.
반드시 true 혹은 false를 반환하며, 기본 값은 true입니다. (false 반환 시 중지)
해당 메소드 안에서 현재 props와 state는 this.props와 this.state로 접근하며, 새로 설정될 props 또는 state는 nextProps와 nextState로 접근할 수 있습니다.
프로젝트 성능을 최적화할 때, 상황에 맞는 알고리즘을 작성하여 리렌더링을 방지할 때는 false값을 반환!
getSnapshotBeforeUpdate 메소드
getSnapshotBeforeUpdate(prevProps, prevState) {
if(prevState.array != this.state.array) {
const { scrollTop, scrollHeight } = this.list;
return { scrollTop, scrollHeight };
}
}
render에서 만들어진 결과물이 브라우저에 실제로 반영되기 직전에 호출됩니다.
해당 메소드에서 반환하는 값은 componentDidUpdate에서 세 번째 파라미터(snapshot) 값으로 전달 받을 수 있습니다.
주로 업데이트하기 직전의 값을 참고할 일이 있을 때 활용 (ex. 스크롤바 위치 유지 ...)
componentDidUpdate 메소드
componentDidUpdate(prevPros, prevState, snapshot) { ... }
리렌더링을 완료한 후 실행하며, 업데이트가 끝난 직후이므로 DOM 관련 처리를 해도 괜찮습니다.
prevPros 혹은 prevState로 컴포넌트가 이전에 가졌던 데이터에 접근할 수 있습니다.
또한 getSnapshotBeforeUpdate에서 반환한 값이 있다면 snapshot 값을 전달 받을 수 있습니다.
componentWillUnmount 메소드
componentWillUnmount() { ... }
컴포넌트를 DOM에서 제거할 때 실행합니다.
componentDidMount에서 등록한 이벤트, 타이머, 직접 생성한 DOM이 있다면 해당 메소드에서 제거 작업을 해야 합니다.
componentDidCatch 메소드
componentDidCatch(error, info) {
this.setState({
error: true
});
console.log({ error, info });
}
컴포넌트 렌더링 도중 에러 발생 시 애플리케이션이 먹통되지 않고 오류 UI를 보여줄 수 있게 해줍니다.
error는 파라미터에 어떤 에러가 발생했는 지 알려주고, info 파라미터는 어디에 있는 코드에서 오류가 발생했는지 알려줍니다.
하지만 컴포넌트 자신에게 발생하는 에러를 잡아낼 수 없고, 자신의 this.props.children으로 전달되는 컴포넌트에서 발생하는 에러만 잡아낼 수 있습니다.
라이프사이클 메소드 사용하기
예제 컴포넌트 생성
LifeCycleSample.js
import { Component } from "react";
class LifeCycleSample extends Component {
state = {
number: 0,
color: null
}
myRef = null; // ref를 설정할 부분
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;
}
componentWillUnmount() {
console.log('componentWillUnmount');
}
handleClick = () => {
this.setState({
number: this.state.number + 1
});
}
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 LifeCycleSample;
각 메소드 실행 시 콘솔 디버거에 기록하고, 부모 컴포넌트에서 props로 색상을 받아 버튼을 누르면 state.number 값을 1씩 증가시켜줍니다.
getDerivedStateFromProps는 부모에게서 받은 color 값을 state에 동기화 시키고 있는 것을 확인할 수 있습니다.
getSnapshotBeforeUpdate는 DOM에 변화가 일어나기 직전의 색상 속성을 snapshot 값으로 반환하여 해당 값을 componentDidUpdate에서 조회할 수 있게 하였습니다.
또한, shouldComponentUpdate 메소드에서 state.number 값의 마지막 자리 수가 4면 리렌더링을 취소하도록 하였습니다.
App 컴포넌트에서 예제 컴포넌트 사용
App.js
import { Component } from 'react';
import './App.css';
import LifeCycleSample from './LifeCycleSample';
// 랜덤색상 생성
function getRandomColor() {
return '#' + Math.floor(Math.random() * 16777215).toString(16);
}
class App extends Component {
state = {
color: '#000000'
}
handleClick = () => {
this.setState({
color: getRandomColor()
});
}
render() {
return (
<div>
<button onClick={this.handleClick}>랜덤 색상</button>
<LifeCycleSample color={this.state.color} />
</div>
)
}
}
export default App;
getRandomColor 함수는 state의 color 값을 랜덤 색상으로 설정합니다.
16777215를 hex로 표현 → ffffff 이므로, 000000부터 ffffff 값을 반환하게 됩니다.
버튼을 누르면 handleClick 메소드가 호출되고, LifeCycleSample 컴포넌트의 color 값을 props로 설정합니다.
※ 콘솔 값이 2번씩 출력됐는데 이는 React.StricMode 때문 적용되어 있어서 그럼
개발 환경에서만 두 번씩 호출되고, 프로덕션 환경에선 정상 호출되지만, 불편하다면 index.js에서 React.StrictMode를 제거
에러 잡아내기
일부로 에러를 발생 시켜 보겠습니다.
LifeCycleSample.js - return
return(
<div>
{this.props.missing.value} {/* 오류 발생 코드 */}
<h1 style={style} ref={(ref) => this.myRef = ref}>{this.state.number}</h1>
<p>color : {this.state.color}</p>
<button onClick={this.handleClick}>더하기</button>
</div>
)
오류가 발생하였고, 화면은 아무것도 나타나지 않는 빈 화면이 렌더링 되는 것을 확인할 수 있습니다.
에러를 잡아주는 ErrorBoundary 컴포넌트를 생성하여 에러가 발생하였다는 것을 사용자에게 알려주도록 하겠습니다.
ErrorBoundary.js
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 함수
render() {
return (
<div>
<button onClick={this.handleClick}>랜덤 색상</button>
<ErrorBoundary>
<LifeCycleSample color={this.state.color} />
</ErrorBoundary>
</div>
)
}
에러 발생 시 componentDidCatch 메소드가 호출되고, state.error 값을 true로 업데이트 해주었습니다.
render 함수에서 에러발생 시 '에러가 발생했습니다!' 문구가 출력되도록 하였고 렌더링이 잘 되는 것을 확인할 수 있습니다.
'JavaScript > React' 카테고리의 다른 글
React) Hooks - useReducer, useMemo (0) | 2022.11.15 |
---|---|
React) Hooks - useState, useEffect (0) | 2022.11.15 |
React) 컴포넌트 반복 (map, filter, key) (0) | 2022.11.14 |
React) ref - 컴포넌트에 이름 달기 (0) | 2022.11.11 |
React) ref - DOM에 이름 달기, state를 이용해 기능 구현 (0) | 2022.11.11 |