컴포넌트의 라이프사이클 메서드

nasagong·2023년 2월 11일
0

React

목록 보기
8/15
post-thumbnail

📚 들어가며

모든 리액트 컴포넌트에는 라이프사이클이 존재한다. 페이지에 렌더링 되기 전 준비과정부터 페이지에서 사라질때까지 컴포넌트의 수명으로 규정하는데, 각각의 단계 전후에서 라이프사이클 메서드를 사용해 특정 작업을 수행할 수 있다.

(이번 장 역시 아직 Hooks를 배우지 않았기에 클래스형으로 실습을 진행한다.)

1. 라이프사이클 메서드의 이해

라이프사이클 메서드는 총 9종류다. 이때 각각의 메서드에는 시점에 따라 접두사가 붙어있는데, Will접두사가 붙은 메서드는 어떤 작업을 작동하기 전, Did 접두사가 붙은 메서드는 작동한 후에 실행된다. 보통은 mount, update, unmount를 기준으로 전후가 나뉜다. 이 세 요소는 라이프사이클을 구성하는 가장 큰 카테고리다.

이제 각각의 라이프사이클에 대해 알아보자.

마운트

DOM이 생성되고 웹 브라우저에 나타나는 것을 마운트라고 부른다. 마운트 과정에서 호출되는 메서드와 순서는 아래와 같다.

  1. [컴포넌트 만들기]
  2. constructor
  3. gerDerivedStateFromProps
  4. render
  5. componentDidMount

constructor : 컴포넌트를 새로 생성할 때마다 호출되는 클래스 생성자 메서드다. 이미 많이 사용해봤다.

getDerivedStateFromProps : props에 있는 값을 state에 넣을 때 사용하는 메서드다.

render: 개발자가 작성한 UI를 렌더링하는 메서드다.
componentDidMount: 컴포넌트가 브라우저 상에 렌더링된 후 호출하는 메서드다.

업데이트

컴포넌트가 다음 네 가지 경우에 업데이트된다.

  1. props가 바뀔 때
  2. state가 바뀔 때
  3. 부모 컴포넌트가 리렌더링될 때
  4. this.focusUpdate로 강제로 렌더링을 트리거할 떄

위와 같은 이유로 컴포넌트가 렌더링되면 아래 메서드들을 호출한다.

  1. getDerivedStateFromProps : 마운트 과정에서도 호출되며 업데이트 작업 전에도 호출된다.props의 변화에 따라 state값에도 변화를 주고 싶을 때 사용한다.
  2. shouldComponentUpdate : 컴포넌트가 리렌더링을 해야하는지 여부를 판단하는 메서드다. boolean값을 반환하며 true면 다음 라이프사이클 메서드를 호출, false면 작업을 중지한다.
  3. render : 항상 쓰는 그 render다.
  4. getSnapShotBeforeUpdate : 컴포넌트 변화를 DOM에 반영하기 바로 직전에 호출하는 메서드다.
    (이후 브라우저상 실제 DOM 변화 발생)
  5. componentDidupdate : 컴포넌트의 업데이트 작업이 끝난 후(Did)호출하는 메서드다.

언마운트

마운트의 반대 과정으로 컴포넌트를 DOM에서 제거하는 것을 의미한다. 사용되는 라이프사이클 메서드는 하나다.

ComponenWillUnmount : 컴포넌트가 웹브라우저 상에서 사라지기 전에(Will) 호출하는 메서드다.

2. 라이프사이클 메서드 살펴보기

위에서 소개된 메서드들에 대해 자세히 알아보자.

getDerivedStateFromProps

props로 받아 온 값을 state에 동기화하는 용도로 사용한다. 설명했듯 컴포넌트가 마운트될 때와 업데이트될 때 호출된다.

static getDeriveStateFromProps(nextProps, prevState){ 
     if(nextProps.value !== prevState.value){
         return {value : nextProps.value };
     return null;
}

일단은 눈으로 살펴만 보자.

ComponentDidMount

componentDidMount() {...}

첫 렌더링을 다 마친 후 실행된다. 이 안에서 다른 자바스크립트 라이브러리나 프레임워크 함수를 호출하거나 비동기작업을 처리한다.

shouldComponentUpdate

shouldComponentUpdate(nextProps, nextState){...}

리렌더링을 시작할지 여부를 지정하는 메서드다. 설명했듯 true/false를 반환한다. 컴포넌트를 만들 때 따로 생성하지 않으면 항상 true값을 반환한다.

메서드 내부에서 현재의 state나 props에 접근할 때는 this.state / this.props로 접근하며, 새로 설정될 것들의 경우는 nextProps와 nextState를 사용한다.

getSnapshotBeforeUpdate

getSnapshotBeforeUpdate(prevProps, prevState){
    if(prevState.array !== this.state.array){
       const {scrollTop, scrollHeight} = this.list
       return {scrollTop, scrollHeight};
     }
}

render에서 만들어진 결과물이 브라우저에 실제로 반영되기 전에 호출된다. 이 메서드가 반환하는 값은 componentDidUpdate에서 세번째 피라미터인 snapshot으로 전달받을 수 있다. 스냅샷을 찍듯 업데이트 직전의 상태를 기록하는 메서드 같다. 예시 코드는 스크롤바 위치를 리턴값의 예로 들었다.

ComponentDidUpdate

componentDidUpdate(prevProps, prevState, snapshot)
{...}

리렌더링이 완료된 후 실행되며 컴포넌트가 리렌더링 이전에 가졌던 값 또는 전달받은 snapshot값에 접근할 수 있다.

componentWillUnmount

componentWillUnmount() {...}

컴포넌트를 DOM에서 제거할 때 실행한다. componentDidMount에서 생성한 등록한 이벤트나 생성한 DOM이 있다면 여기서 제거해야 한다.

componentDidCatch

componentDidCatch(error, info){
     this.setState({
         error: true
     });
     console.log({error,info});
}

컴포넌트 렌더링 도중 에러가 catch됐을 때 호출된다. 애플리케이션이 에러로 인해 죽지 않고 오류 UI를 보여 줄 수 있게 한다. 나중에 에러 파트에서 자세히 배우게 된다.


솔직히.. 일단 정리하는 입장이니까 다 적어보긴 했다만.. 이걸 암만 읽어봐도 실습해보기 전엔 뭔 소린지 하나도 모르는 게 정상같다.. 이젠 직접 라이프사이클 메서드를 사용해보며 직접 이해해보자.. 아 책 내용 다 정리하는 스타일은 별로 같은디 암만 생각해도

3. 라이프사이클 메서드 사용하기

이제 직접 사용해보자. 역시 보는 게 빠르다.

// LifeCycleSample.js
import { Component } from 'react';

class LifeCycleSample extends Component{
  state = {
    number : 0,
    color : null,
  }
  myRef = null;

  constructor(props){
    super(props);
    console.log('constructor');
  }
  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);
    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; 
// App.js
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;

조금 길지만.. 차근차근 따라가면 하나도 어려울 게 없다.
렌더링이 완료된 화면은 아래와 같다.

우선 LifeCycleSample컴포넌트에는 color값이 props로 넘겨지며, App컴포넌트의 버튼을 눌러 값이 변할때 마다 라이프사이클 컴포넌트도 업데이트 된다. 이 때 콘솔창을 보면 getDerivedStateFromProps가 찍힌다. 메소드 이름 너무 길다 치기 귀찮다.. 아무튼. nextProps와 prevState.color의 값이 다를 때마다 호출된다는것. number값은 컴포넌트 자체의 state기에 이 메서드와는 상관 없다.

shouldComponentUpdate도 본인의 역할을 하고 있는데, number의 일의 자리가 4 경우 리렌더링 하지 않도록 false값을 반환하고 있다. 실제로 3 이후 +1을 눌러도 리렌더링 되지 않고, 한 번 더 눌러야 5가 렌더링되는 것을 확인할 수 있다.

외에도 색상이 업데이트 될 때마다 스냅샷이 저장되고, 업데이트 후 ComponentDidUpdate가 콘솔에 값을 찍어내는 걸 확인할 수 있다.

뭐가 이것저것 많아서 복잡해보이긴 하지만 직접 콘솔을 확인해보며 따라간다면 어려운 부분은 없는 예제였다. 뭘 더 적기엔 메서드 이름들이 너무도 길다

에러 잡아내기

render(){
    {this.props.missing.value}
    console.log('render');
  ...
}

라이프사이클 메서드에 의도적으로 존재하지 않는 값을 조회하도록 만들어 에러를 발생시켰다. 이런 경우엔 화면에 아무것도 렌더링되지 않기에 에러가 발생했다는 것을 사용자에게 인식시켜야 한다. 자바스크립트 Promise에서 catch를 통해 에러를 관리한 것처럼 리액트 또한 componentDidCatch메서드가 존재한다.

에러를 캐치하고 싶은 컴포넌트를 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
import { Component } from 'react';
import LifeCycleSample from './LifeCycleSample';
import ErrorBoundary from './ErrorBoundary';

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>
        <ErrorBoundary>
          <LifeCycleSample color={this.state.color}/>
        </ErrorBoundary>
      </div>
      
    );
  }
}
export default App;

[사진]

에러가 발생하면 준비된 값을 렌더링하고,
발생하지 않았다면 childrenprops인 라이프사이클 컴포넌트를 렌더링한다. 이런 식으로 컴포넌트 자체의 에러를 캐치한 후 처리할 수 있다.


🙃 마치며..

라이프사이클 메서드를 사용하는 법을 간단하게 알아봤다. 필요에 따라 찾아봐가며 사용하면 될 것 같다. 다음장에서 Hooks를 배운 후에는 함수형 컴포넌트에 다른 방법으로 사용하게 될거라 일단은 알아만 두자.

profile
잘쫌해

0개의 댓글