React 컴포넌트와 라이프사이클 이벤트

라이프 사이클 이벤트란

React의 컴포너트는 생명주기(Life cycle)을 가진다. 생명주기란 컴포넌트가 생성되고 사용되고 소멸될 때 까지 일련의 과정을 말한다.
이러한 생명주기 안에서는 특정 시점에 자동으로 호출되는 메서드가 있는데, 이를 라이프 사이클 이벤트라고 한다.

React v16.3 이전의 라이프 사이클 이벤트

old_react_lifecycle

class Content extends React.Component {
  constructor(props) {
    super(props);
  }
  componentWillMount() {
    console.log('Component WILL MOUNT!')
  }
  componentDidMount() {
    console.log('Component DID MOUNT!')
  }
  componentWillReceiveProps(nextProps) {    
    console.log('Component WILL RECIEVE PROPS!')
  }
  shouldComponentUpdate(nextProps, nextState) {
    return true;
  }
  componentWillUpdate(nextProps, nextState) {
    console.log('Component WILL UPDATE!');
  }
  componentDidUpdate(prevProps, prevState) {
    console.log('Component DID UPDATE!')
  }
  componentWillUnmount() {
    console.log('Component WILL UNMOUNT!')
  }
  render() {
    return (
        <div>
          <h1>{this.props.sentDigit}</h1>
        </div>
    );
  }
}

constructor

constructor(props){
  super(props);
}

생성자 메소드로 컴포넌트가 생성될 때 단 한번만 실행된다.
이 메소드에서만 state를 설정할 수 있다.

componentWillMount()

componentWillMount(){
  console.log("componentWillMount");
}

React 엘리먼트를 실제 DOM 노드에 추가하기 직전에 호출되는 메소드다.
DOM이 생성되지 않았으므로 DOM을 조작할 수 없고, render가 호출되기 전이기 때문에 setState를 사용해도 render가 호출하지 않는다.

render()

컴포넌트 렌더링을 담당한다.

componentDidMount()

componentDidMount(){
  console.log("componentDidMount");
}

컴포넌트가 만들어지고 render가 호출된 이후에 호출되는 메소드다.
AJAX나 타이머를 생성하는 코드를 작성하는 부분이다.

componentWillReceiveProps(nextProps)

componentWillReceiveProps(nextProps){
  console.log("componentWillReceiveProps: " + JSON.stringify(nextProps));
}

컴포넌트 생성후에 첫 렌더링을 마친 후 호출되는 메서드다.
컴포넌트가 처음 마운트 되는 시점에서는 호출되지 않는다.
props를 받아서 state를 변경해야 하는 경우 유용하다.
이 메소드 내부에서 setState를 사용해도 추가적인 렌더링이 발생하지 않는다.

shouldComponentUpdate(nextProps, nextState)

shouldComponentUpdate(nextProps, nextState){
  console.log("shouldComponentUpdate: " + JSON.stringify(nextProps) + " " + JSON.stringify(nextState));
  return true;
}

컴포넌트 업데이트 직전에 호출되는 메소드다.
props 또는 state가 변경되었을 때, 재랜더링을 여부를 return 값으로 결정한다.

componentWillUpdate(nextProps, nextState)

componentWillUpdate(nextProps, nextState){
  console.log("componentWillUpdate: " + JSON.stringify(nextProps) + " " + JSON.stringify(nextState));
}

shouldComponentUpdate가 불린 이후에 컴포넌트 업데이트 직전에서 호출되는 메소드다.
새로운 props 또는 state가 반영되기 직전 새로운 값들을 받는다.
이 메서드 안에서 this.setState()를 사용하면 무한 루프가 일어나게 되므로 사용하면 안된다.

componentDidUpdate(prevProps, prevState)

componentDidUpdate(prevProps, prevState){
  console.log("componentDidUpdate: " + JSON.stringify(prevProps) + " " + JSON.stringify(prevState));
}

컴포넌트 업데이트 직후에 호출되는 메소드다.

componentWillUnmount()

componentWillUnmount(){
  console.log("componentWillUnmount");
}

컴포넌트가 소멸된 시점에(DOM에서 삭제된 후) 실행되는 메소드다.
컴포넌트 내부에서 타이머나 비동기 API를 사용하고 있을 때, 이를 제거하기에 유용하다.

시계 예제

Component LifeCycle API
React 컴포넌트와 컴포넌트 라이프사이클

React v16.3 이후에 변경된 부분

  • componentWillMount, componentWillReceiveProps, componentWillUpdate를 v17 부터 사용불가
  • componentWillReceiveProps 대체 메서드 추가 getDerivedStateFromProps
  • componentWillUpdate 대체 메서드 추가 getSnapshotBeforeUpdate
  • componentDidCatch 컴포넌트 에러 핸들링 API 추가

0_oodfq7pzaqg6yeth_

변경 이유

  • 초기 렌더링을 제어하는 방법이 많아져서 혼란이 됨.
  • 오류 처리 인터럽트 동작시에 메모리 누수 발생할 수 있음.
  • React 커뮤니티에서도 가장 혼란을 야기하는 라이프 사이클

Component Lifecycle Changes

getDerivedStateFromProps

getDerivedStateFromPropscomponentWillReceiveProps의 대체 역할로 작성된 메서드로 컴포넌트가 인스턴스화 된 후, 새 props를 받았을 때 호출된다. 주의할 점으로 setState를 사용하는 것이 아닌 값을 retrun 해야한다. state를 갱신하는 객체를 반환할 수 있고, 새로운 propsstate 갱신을 필요로 하지 않음을 나타내기 위해 null을 반환할 수도 있다.

  • 이전 코드

    componentWillReceiveProps(nextProps) {
    if (this.props.name !== nextProps.name) {
      this.setState({ name: nextProps.name });
    }
    }
    
  • 개선된 코드

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

getSnapshotBeforeUpdate

getSnapshotBeforeUpdatecomponentWillUpdate의 대체 역할로 작성된 메서드로 DOM이 업데이트 직전에 호출된다. (이 라이프 사이클은 많이 필요하지 않지만, 렌더링되는 동안 수동으로 스크롤 위치를 유지해야할 때와 같은 경우에는 유용할 수 있다)

  • 이전 코드

    componentWillUpdate(nextProps, nextState) {
    if (this.props.list.length < nextProps.list.length) {
      this.previousScrollOffset =
        this.listRef.scrollHeight - this.listRef.scrollTop;
    }
    }
    
  • 개선된 코드

    getSnapshotBeforeUpdate(prevProps, prevState) {
    if (prevProps.list.length < this.props.list.length) {
      return (
        this.listRef.scrollHeight - this.listRef.scrollTop
      );
    }
    return null;
    }
    

componentDidCatch

컴포넌트 오류 처리 개선을 위해 추가되었다.
에러 발생시에 state를 변경하고 render에서 해당 처리를 구현하면 된다.
주의할점은 자식 컴포넌트에서 발생하는 에러만 잡아낼 수 있고, 자신의 에러는 잡아낼수 없다.

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

  componentDidCatch(error, info) {
    // Display fallback UI
    this.setState({ hasError: true });
    // You can also log the error to an error reporting service
    logErrorToMyService(error, info);
  }

  render() {
    if (this.state.hasError) {
      // You can render any custom fallback UI
      return <h1>Something went wrong.</h1>;
    }
    return this.props.children;
  }
}

에러 처리 예제

버전별 계획

  • 16.3: 안전하지 않은 라이프 사이클에 대한 별칭 소개, UNSAFE_componentWillMount, UNSAFE_componentWillReceiveProps, UNSAFE_componentWillUpdate. (이 릴리즈에서는 이전 라이프 사이클 이름과 새로운 별칭이 모두 동작한다)

  • 향후 16.x release: componentWillMount,componentWillReceiveProps,componentWillUpdate에 대한 제거 예정 경고가 활성화된다. (이 릴리즈에서는 이전 라이프 사이클 이름과 새 별칭이 모두 작동하지만 이전 이름은 DEV 모드 경고를 기록한다)

  • 17.0 :componentWillMount,componentWillReceiveProps 및componentWillUpdate가 제거됨. (오직 "UNSAFE_"으로 시작하는 새로운 라이프 사이클만이 동작 할 것이다)

reading-dom-properties-before-an-update

참고