TIL 020 React Life Cycle

Alice Kim·2021년 5월 16일
1

TIL

목록 보기
20/23
post-thumbnail

🎯 컴포넌트가 여러분의 브라우저에서 나타날때, 사라질때, 그리고 업데이트 될 때, 호출되는 API들 공부!

1. 컴포넌트 초기 생성

constructor()

constructor(props) {
  super(props);
}
  • 컴포넌트 생성자 함수.
  • 컴포넌트가 새로 만들어질 때마다 이 함수가 호출

componentDidMount()

componentDidMount() {
  // 외부 라이브러리 연동: D3, masonry, etc
  // 컴포넌트에서 필요한 데이터 요청: Ajax, GraphQL, etc
  // DOM 에 관련된 작업: 스크롤 설정, 크기 읽어오기 등
}
  • 컴포넌트가 화면에 나타나게 됐을 때 호출되는 API.
  • DOM 을 사용해야하는 외부 라이브러리 연동: D3, masonry etc
  • 해당 컴포넌트에서 필요로하는 데이터를 요청하기 위해 axios, fetch 등을 통하여 ajax, GraphQL 등을 요청을 하거나,
  • DOM 의 속성을 읽거나 직접 변경하는 작업을 진행(스크롤 설정, 크기 읽어오기)

2. 컴포넌트 업데이트

  • 컴포넌트 업데이트는 props 의 변화, 그리고 state 의 변화에 따라 결정
  • 업데이트가 되기 전과 후 각각 API가 호출됨

static getDerivedStateFromProps()

static getDerivedStateFromProps(nextProps, prevState) {
  // 여기서는 setState 를 하는 것이 아니라
  // 특정 props 가 바뀔 때 설정하고 설정하고 싶은 state 값을 리턴하는 형태로
  // 사용됩니다.
  
  if (nextProps.value !== prevState.value) {
    return { value: nextProps.value };
  }
  return null; // null 을 리턴하면 따로 업데이트 할 것은 없다라는 의미
  
}
  • 이 API 는 props 로 받아온 값을 state 로 동기화 하는 작업을 해줘야 하는 경우에 사용

shouldComponentUpdate()

shouldComponentUpdate(nextProps, nextState) {
	 if (nextState.number % 5 === 0) return false;//업데이트를 안함
    return true;
	} 
}

//이 함수는 기본적으로는 true를 반환!
  • 이 API 는 컴포넌트를 최적화하는 작업에서 매우 유용
  • 리액트에서는 변화가 발생하는 부분만 업데이트를 해줘서 성능이 꽤 잘 나온다고 했었지만, 변화가 발생한 부분만 감지해내기 위해서는 Virtual DOM 에 한번 그려줘야한다.
  • 현재 컴포넌트의 상태가 업데이트되지 않아도, 부모 컴포넌트가 리렌더링되면, 자식 컴포넌트들도 렌더링
  • “렌더링” 된다는건, render() 함수가 호출된다는 의미
  • 변화가 없으면 DOM 조작은 하지 않고 그저 Virutal DOM 에만 렌더링 할 뿐
  • 이 작업은 그렇게 부하가 많은 작업은 아니지만, 컴포넌트가 무수히 많이 렌더링된다면 쓸데없이 낭비가 발생하게 됨. 왜냐, VDOM도 CPU 자원을 어느정도 사용하고 있긴 하므로!
  • 쓸대없이 낭비되고 있는 이 CPU 처리량을 줄여주기 위해서 불필요하게 Virtual DOM에 리렌더링 하는것을 방지하기 위해서 shouldComponentUpdate 를 작성
  • 이 함수는 기본적으로 true 를 반환
  • 작성해주는 조건에 따라 false 를 반환하면 해당 조건에는 render 함수를 호출하지 않음

componentWillUpdate

componentWillUpdate(nextProps, nextState) {

}

//이 다음에 render()
  • 이 API는 shouldComponentUpdate 에서 true 를 반환했을때만 호출
  • 주로 애니메이션 효과를 초기화하거나, 이벤트 리스너를 없애는 작업
  • 이 함수가 호출되고난 다음에는, render() 가 호출
  • 이 API 또한 v16.3 이후 deprecate 됩니다. 기존의 기능은 getSnapshotBeforeUpdate 로 대체될 수 있음

getSnapshotBeforeUpdate()

getSnapshotBeforeUpdate(prevProps, prevState) {
    // DOM 업데이트가 일어나기 직전의 시점입니다.
    // 새 데이터가 상단에 추가되어도 스크롤바를 유지해보겠습니다.
    // scrollHeight 는 전 후를 비교해서 스크롤 위치를 설정하기 위함이고,
    // scrollTop 은, 이 기능이 크롬에 이미 구현이 되어있는데, 
    // 이미 구현이 되어있다면 처리하지 않도록 하기 위함입니다.
    if (prevState.array !== this.state.array) {
      const {
        scrollTop, scrollHeight
      } = this.list;

      // 여기서 반환 하는 값은 componentDidMount 에서 snapshot 값으로 받아올 수 있습니다.
      return {
        scrollTop, scrollHeight
      };
    }
  }

이 API 가 발생하는 시점 확인!

  1. render()
  2. getSnapshotBeforeUpdate()
  3. 실제 DOM 에 변화 발생
  • 이 API를 통해서, DOM 변화가 일어나기 직전의 DOM 상태를 가져오고, 여기서 리턴하는 값은 componentDidUpdate 에서 3번째 파라미터로 받아올 수 있게 됨

componentDidUpdate()


componentDidUpdate(prevProps, prevState, snapshot) { }
  • 이 API는 컴포넌트에서 render() 를 호출하고난 다음에 발생
  • 이 시점에선 this.props 와 this.state 가 바뀌어 있음
  • 파라미터를 통해 이전의 값인 prevProps 와 prevState 를 조회 할 수 있다.
  • getSnapshotBeforeUpdate 에서 반환한 snapshot 값은 세번째 값으로 받아옴

  componentDidUpdate(prevProps, prevState, snapshot) {
    if (snapshot) {
      const { scrollTop } = this.list;
      if (scrollTop !== snapshot.scrollTop) return; // 기능이 이미 구현되어있다면 처리하지 않습니다.
      const diff = this.list.scrollHeight - snapshot.scrollHeight;
      this.list.scrollTop += diff;
    }
  }

전체 코드

import React, { Component } from "react";
import "./ScrollBox.css";

class ScrollBox extends Component {
  id = 2;

  state = {
    array: [1]
  };

  handleInsert = () => {
    this.setState({
      array: [this.id++, ...this.state.array]
    });
  };

  getSnapshotBeforeUpdate(prevProps, prevState) {
    // DOM 업데이트가 일어나기 직전의 시점입니다.
    // 새 데이터가 상단에 추가되어도 스크롤바를 유지해보겠습니다.
    // scrollHeight 는 전 후를 비교해서 스크롤 위치를 설정하기 위함이고,
    // scrollTop 은, 이 기능이 크롬에 이미 구현이 되어있는데,
    // 이미 구현이 되어있다면 처리하지 않도록 하기 위함입니다.
    if (prevState.array !== this.state.array) {
      const { scrollTop, scrollHeight } = this.list;

      // 여기서 반환 하는 값은 componentDidMount 에서 snapshot 값으로 받아올 수 있습니다.
      return {
        scrollTop,
        scrollHeight
      };
    }
  }

  componentDidUpdate(prevProps, prevState, snapshot) {
    if (snapshot) {
      const { scrollTop } = this.list;
      if (scrollTop !== snapshot.scrollTop) return; // 기능이 이미 구현되어있다면 처리하지 않습니다.
      const diff = this.list.scrollHeight - snapshot.scrollHeight;
      this.list.scrollTop += diff;
    }
  }

  render() {
    const rows = this.state.array.map(number => (
      <div className="row" key={number}>
        {number}
      </div>
    ));

    return (
      <div>
        <div
          ref={ref => {
            this.list = ref;
          }}
          className="list"
        >
          {rows}
        </div>
        <button onClick={this.handleInsert}>Click Me</button>
      </div>
    );
  }
}

export default ScrollBox;

3. 컴포넌트 제거

컴포넌트가 더 이상 필요하지 않게 되면 단 하나의 API 가 호출

componentWillUnmount()

componentWillUnmount() {
  // 이벤트, setTimeout, 외부 라이브러리 인스턴스 제거
}
  • 등록했었던 이벤트를 제거
  • setTimeout 을 걸은것이 있다면 clearTimeout 을 통하여 제거
  • 외부 라이브러리를 사용한게 있고 해당 라이브러리에 dispose 기능이 있다면 여기서 호출

https://s3-us-west-2.amazonaws.com/secure.notion-static.com/4ff23712-7ce4-4d50-978a-4573365efa76/Untitled.png

4. 컴포넌트에 에러 발생

componentDidCatch

componentDidCatch(error, info) {
  this.setState({
    error: true
  });
}
  • 에러가 발생하면 이런식으로 componentDidCatch 가 실행되게 하고, state.error 를 true 로 설정하게 하고, render 함수쪽에서 이에 따라 에러를 띄워주면 됨.
import React, { Component } from 'react';

const Promblematic = () => {
  throw (new Error('버그가 나타났다!'));
  return (
    <div>
      
    </div>
  );
};

class Counter extends Component {
  state = {
    number: 0,
    error: false
  }

  // (...)
  
  componentDidCatch(error, info) {
    this.setState({
      error: true
    });
  }
  
  render() {
    if (this.state.error) return (<h1>에러발생!</h1>);

    return (
      <div>
        <h1>카운터</h1>
        <div>: {this.state.number}</div>
        { this.state.number === 4 && <Promblematic /> }
        <button onClick={this.handleIncrease}>+</button>
        <button onClick={this.handleDecrease}>-</button>
      </div>
    );
  }
}

export default Counter;
profile
If you don't build your dream, someone will hire you to help build theirs.

1개의 댓글

comment-user-thumbnail
2021년 5월 16일

주로 언제 쓰이는지까지 같이 있어서 이해가 더 잘 돼요👍👍👍👍👍

답글 달기