[React] Lifecycle (2)

Component Lifecycle

이전 시간에 이어서 리액트 라이프사이클에 대해 알아보도록 합시다. 이번에는 16.3 버전 이상의 변경된 내용에 대해 살펴봅니다.

또한, 출처는 React 공식 문서를 참고하니 더욱 자세한 내용은 반드시 공식 문서를 참조해주시기 바랍니다.

라이프사이클 변경 사항(v16.3 이상)

  • constructor
  • componentWillMount => getDerivedStateFromProps
  • render
  • componentDidMount

  • componentWillReceiveProps => getDerivedStateFromProps
  • shouldComponentUpdate
  • render
  • componentWillUpdate => getSnapshotBeforeUpdate
  • DOM에 적용
  • componentDidUpdate

  • componentWillUnmount

1. getDerivedStateFromProps

getDerivedStateFromProps는 최초 마운트 시와 갱신 시 모두에서 render 메소드를 호출하기 직전에 호출됩니다. state를 갱신하기 위한 객체를 반환하거나, null을 반환하여 아무 것도 갱신하지 않을 수 있습니다.

이 메소드는 시간이 흐름에 따라 변하는 props에 state가 의존하는 아무 드문 사용례를 위하여 존재합니다. 예를 들어, 무엇을 움직이도록 만들지 결정하기 위하여 이전과 현재의 자식 엘리먼트를 비교하는 <Transition>와 같은 컴포넌트를 구현할 때에 편리하게 사용할 수 있습니다.

1.1 코드 예시

static getDerivedStateFromProps(nextProps, prevState) {
  console.log("getDerivedStateFromProps", nextProps, prevState);

  return {
    age: 390,
  }
}

ReactDOM.render(<App name="Orosy" />, document.querySelector("#root"));

2. getSnapshotBeforeUpdate

getSnapshotBeforeUpdate는 가장 마지막으로 렌더링된 결과가 DOM 등에 반영되었을 때에 호출됩니다. 이 메소드를 사용하면 컴포넌트가 DOM으로부터 스크롤 위치 등과 같은 정보를 이후 변경되기 전에 얻을 수 있습니다. 이 라이프사이클 메소드가 반환하는 값은 componentDidUpdate에 인자로 전달됩니다.

이 메소드에 대한 사용례는 흔하지 않지만, 채팅 화면처럼 스크롤 위치를 따로 처리하는 작업이 필요한 UI 등을 생각해볼 수 있습니다.

스냅샷 값을 반환하거나 null을 반환합니다.

2.1 코드 예시

아래의 코드는 위에서 언급했던 것처럼 getSnapshotBeforeUpdate를 이용하여 데이터가 업데이트하게 되면 스크롤 위치가 자동으로 변하게 되는 로직을 구현한 코드입니다.

실제로 코드는 이해하기 쉽지 않기 때문에 이러한 방식으로 해당 라이프사이클을 활용하는 것이라고 알아주시면 됩니다.

let i = 0;

class App extends React.Component {
  state = { list: [] };

  render() {
    return (
      <div id="list" style={{ height: 100, overflow: "scroll" }}>
        {this.state.list.map((i) => {
          return <div>{i}</div>;
        })}
      </div>
    );
  }

  componentDidMount() {
    setInterval(() => {
      this.setState((state) => ({
        list: [...state.list, i++],
      }));
    }, 1000);
  }

  getSnapshotBeforeUpdate(prevProps, prevState) {
    if (prevState.list.length === this.state.list.length) return null;
    const list = document.querySelector("#list");
    return list.scrollHeight - list.scrollTop;
  }

  componentDidUpdate(prevProps, prevState, snapshot) {
    console.log(snapshot);
    if (snapshot === null) return;
    const list = document.querySelector("#list");
    list.scrollTop = list.scrollHeight - snapshot;
  }
}

ReactDOM.render(<App name="Orosy" />, document.querySelector("#root"));

3. componentDidCatch

componentDidCatch는 이름에서 알 수 있다시피 자손 컴포넌트에서 오류가 발생했을 때에 호출되는 메소드입니다.

해당 라이프사이클이 나오기 전에는 리액트 앱으로 부분적으로 에러가 발생했을 때, 전체 웹사이트가 동작하지 않는 문제가 있었습니다. 해당 컴포넌트로 오류가 발생하게 되면 해당 화면을 보여줄 수 있게 되었습니다.

그러나 해당 라이프사이클의 단점은 자손 컴포넌트가 아닌 자기 자신에게 오류가 발생하게 되면, 오류를 캐치할 수 없게 됩니다. 그렇기 때문에 에러 바운더리를 가장 부모로 만들어주고 그 하위인 서비스 컴포넌트에서 문제가 발생했을 때에 캐치하는 방식으로 사용하게 됩니다. 그러므로 이 경우에는 에러 바운더리라는 라이브러리를 사용해야 합니다.

3.1 코드 예시

class App extends React.Component {
  state = {
    hasError: false,
  };
  render() {
    if (this.state.hasError) {
      return <div>에러 발생!!</div>;
    }
    return <WebService />;
  }
  componentDidCatch(error, info) {
    this.setState({ hasError: true });
  }
}

좋은 웹페이지 즐겨찾기