ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [React.js] Lifecycle
    Front-End(Web)/React - 프레임워크(React, Next) 2020. 11. 27. 17:14
    반응형

    🤷 Lifecycle 이란?

    리액트 컴포넌트는 생명주기(Lifecycle)를 가진다. (created, rendered, added to DOM, updated, removed)

    Lifecycle은 크게 세 가지(Mounting, Updating, Unmounting)로 분류할 수 있으며, 아래 그림을 참고하자.

    출처: zerocho 님의 블로그

     


    📒 Lifecycle Methods

    컴포넌트에는 Lifecycle을 다루기 위한 단계별 메소드들이 존재한다. (v16.3 이전 버전)

    출처: kyusung 님의 블로그(velog)

     

    • Mounting: 컴포넌트가 실행되고 DOM에 추가되는 과정이다. 컴포넌트가 실행되면 우선 context, defaultProps, satae를 결정하고, 'componentWillMount' 메소드를 호출한다. 이후 'render'로 DOM에 컴포넌트를 부착하면, 'componentDidMount' 메소드를 호출하면서 Mount 동작이 완료된다.
    • Updating: props를 받거나 state가 변하면서 컴포넌트가 업데이트되는 과정이다. 우선 컴포넌트의 업데이트를 감지하면 'componentWillReceiveProps' 메소드가 호출되고, 'shouldComponentUpdate' 메소드에서 실행여부를 판단한다. 업데이트를 진행한다면 'componentWillUpdate' 메소드가 호출된 두 업데이트가 완료되면, render과 동시에 'componentDidUpdate' 메소드가 호출되면서 Update 동작이 완료된다. state가 변할 경우엔 'componentWillReceiveProps' 생략!
    • Unmounting: 컴포넌트가 DOM에서 제거되는 과정이다. 삭제될 컴포넌트에 대해 'componentWillUnmount' 메소드를 호출한다. Unmount를 통해 이벤트리스너 등 컴포넌트와 연결된 요소들을 정리하며, 동작이 완료되면 따로 DidMount를 호출하진 않음.Unmount가 끝난 컴포넌트는 다시 Re-mount 되거나 하지 않는다.(Lifecycle 종료)

     

    - Mounting 동작

     

    state 공부때 활용한 기본적인 컴포넌트 Mount 구조(constructor() -> render())에서, Mount 이후 동작(componentDidMount())을 추가한 것이다.

    import React from 'react';
    import ReactDOM from 'react-dom';
    
    class Clock extends React.Component {
      // 1. constructor() 메소드: 컴포넌트 Mount 시작
      constructor(props) {
        super(props);
        this.state = { date: new Date() };
      }
      
      // 2. render() 메소드: 컴포넌트 DOM 표현
      render() {
        return <div>{this.state.date.toLocaleTimeString()}</div>;
      }
      
      // 3. componentDidMount() 메소드: Mount 완료 + 이후 동작(setInterval)
      componentDidMount() {
        const oneSecond = 1000;
        setInterval(() => {
          this.setState({ date: new Date() });
        }, oneSecond);
      }
    }
    
    ReactDOM.render(<Clock />, document.getElementById('app'));

    * 위 코드는 시계 컴포넌트로 기본적인 초세기를 해야하므로, 업데이트가 아닌 componentDidMount() 이후 바로 this.setState로 최신화하는 것이다.

     

     

    - Unmounting 동작

     

    컴포넌트를 제거(Unmount)할 때, 동작(위에선 setInterval)을 제거해주지 않으면 App 효율이 떨어지며 아래같은 메세지가 발생한다.

    Warning: setState(...): Can only update a mounted or mounting component. 
    This usually means you called setState() on an unmounted component. 
    This is a no-op. Please check the code for the undefined component.

     

    따라서, Clock 컴포넌트 안에 componentWillUnmount() 메소드를 추가해준다. 여기선, clearInterval(인터벌명)이 적합하다.

      // 3. componentDidMount() 메소드: Mount후 동작이며, 인터벌 제거를 위해 this.intervalID로 명명
      componentDidMount() {
        const oneSecond = 1000;
        this.intervalID = setInterval(() => {
          this.setState({ date: new Date() });
        }, oneSecond);
      }
      
      // 4. componentWillUnmount() 메소드: 컴포넌트가 Unmount 된 뒤의 동작. clearInterval()
      componentWillUnmount() {
        clearInterval(this.intervalID)
      }

     

     

    - Updating 동작

    import React from 'react';
    
    export class Clock extends React.Component {
      // 1. 생성자 메소드(함수)
      constructor(props) {
        super(props);
        this.state = { date: new Date() };
      }
      
      // 3-2. 인터벌 코드중복을 없애기 위한 메소드화
      startInterval(time) {
        this.intervalID = setInterval(() => {
          this.setState({ date: new Date() });
        }, time);
      }
      
      // 2. 렌더링 메소드 (isPrecise 모드 토글기능 추가)
      render() {
        return (
          <div>
            {this.props.isPrecise
              ? this.state.date.toISOString()
              : this.state.date.toLocaleTimeString()}
          </div>
        );
      }
      
      // 3-1. 마운팅 후 메소드
      componentDidMount() {this.startInterval(1000);}
      
      // 4. 업데이트(isPrecise 토글기능) 확인하여 인터벌 변경 **
      componentDidUpdate(prevProps) {
        if (this.props.isPrecise === prevProps.isPrecise) {
          return;
        }
        else {
          clearInterval(this.intervalID);
          this.startInterval(100);
        }
      }
      
      // 5. 언마운팅 메소드(인터벌 제거)
      componentWillUnmount() {
        clearInterval(this.intervalID);
      }
    }

     

    3-2로 만든 startInterval() 메소드 내, this.setState 부분에서 state를 계속 최신화(Update) 하고 있다.

    또한, 토글링을 통해 props.isPrecise에 변화(Update)가 있는 경우, 인터벌을 바꿔주는 componentDidUpdate() 메소드를 추가했다. 


    📒 React v16.3 이후로 변경된 부분

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

    출처: zerocho 님의 블로그

     

    - 변경이유

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

    - getDerivedStateFromProps()

    componentWillReceiveProps() 메소드를 대체. 컴포넌트가 인스턴스화(표현)된 후 새로운 props를 받을 경우 호출된다. 변경된 state는 this.state가 아닌 return으로 반환한다

    // 변경 전: componentWillReceiveProps(next)
    componentWillReceiveProps(nextProps) {
      if (this.props.name !== nextProps.name) {
        this.setState({ name: nextProps.name });
      }
    }
    
    
    // 변경 후: getDerivedStateFromProps(next, prev)
    static getDerivedStateFromProps(nextProps, prevState) {
      if (prevState.name !== nextProps.name) {
        return { name: nextProps.name };
      }
    
      return null;
    }

     

    - getSnapshotBeforeUpdate()

    componentWillUpdate() 메소드를 대체. 업데이트 후 DOM 표현 직전에 호출된다. (자주 사용되진 않음, 렌더링 중 스크롤 유지 등)

    // 변경 전: componentWillUpdate(props, state) 
    componentWillUpdate(nextProps, nextState) {
      if (this.props.list.length < nextProps.list.length) {
        this.previousScrollOffset =
          this.listRef.scrollHeight - this.listRef.scrollTop;
      }
    }
    
    
    // 변경 후: getSnapshotBeforeUpdate(props, state)
    getSnapshotBeforeUpdate(prevProps, prevState) {
      if (prevProps.list.length < this.props.list.length) {
        return (
          this.listRef.scrollHeight - this.listRef.scrollTop
        );
      }
      return null;
    }

     

    - componentDidCatch

    컴포넌트 오류 개선을 위한 메소드. 에러가 발생할 때 state를 발생시키고 특정 조건을 표현한다. (자식 컴포넌트 에러만 감지가능!)

    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;
      }
    }

     

    출처: https://velog.io/@kyusung/ (kyusung 님의 velog)

    컴포넌트의 Lifecycle 개념이 매우 중요하다고 알았고, 난해하진 않지만 각 단계에서 주로 쓰이는 메소드들을 숙지해야겠다.

    또한, props를 받아 state 변경하는 과정 전체는 codecademy 예시에서 보지 못했는데 프로젝트를 진행하며 익혀가야겠다.

    반응형

    'Front-End(Web) > React - 프레임워크(React, Next)' 카테고리의 다른 글

    [React.js] Hooks - useEffect()  (0) 2020.11.28
    [React.js] Hooks - useState()  (0) 2020.11.27
    [React.js] State  (0) 2020.11.26
    [React.js] Props  (0) 2020.11.26
    [React.js] Components Interact, Import/Export  (0) 2020.11.26
Designed by Tistory.