Vue.js Life Cycle 정리

Updated:

Vue.js 생명주기? Life Cycle? 중요한걸까?

맨 처음 Jhipster를 통해 Vue.js로 프론트 엔드를 커스터 마이징하면서 가장 의문을 품었던 영역이 바로 생명주기였다. 그 이유는, 프론트 엔드 개발 경험이 전혀 없던 나는 DOM이 대체 무엇인지, DOM을 알고 나서는 이게 Vue.js에서 어떤 순서로 연결이 되는 건지 모르는 것 투성이였기 때문이다.

특히, 백엔드든 프론트엔드든 부모-자식관계는 개발에서 빠지지 않고 나온다. Vue.js의 생명주기를 직접 검색하고 공부하기 전, 기존의 백엔드 개발 등의 경험으로 짐작하여 당연히 부모가 자식보다 먼저 동작하고 그렇기 때문에 부모의 컴포넌트가 생성되면 당연히 자식에서 부모의 컴포넌트를 사용할 수 있겠지?! 라고 생각했다.

하지만 Vue.js는 달랐다. 라이프 사이클에서 Created 와 Mounted 의 차이, 부모와 자식관계에서 발생하는 라이프 사이클의 차이 등 기존에 생각하던 것과 다른 부분이 많았다. 그래서 인지 나의 개발의도와는 다르게 프론트가 동작하거나 아예 동작하지 않거나, 알 수 없는 에러만을 미친듯이 뿜어내기도 했다.

그 후, Vue.js의 라이프 사이클을 공부해서 개발에 적용하였고 이전보다 Vue.js의 이해도가 높아지면서 훨~씬 개발이 수월해짐을 경험하였다. (역시 이래서 기초가 가장 중요하다고 하는 것 같다.)

따라서, 이를 한번은 정리해야겠다 생각하였고 포스팅을 하게 되었다.

그렇다면 이제 한번 Vue.js의 Life Cycle에 대해 알아보자.

Vue.js의 LifeCycle

Vue.js의 공식홈페이지에서 제공하는 LifeCycle의 도식은 아래 이미지와 같다.

먼저, Vue.js의 생명주기는 크게 4가지 Creation, Mounting, Updating, Destruction 으로 나눌 수 있다.

Creation

Creation 단계에서 실행되는 훅(hook)은 라이프 사이클 중 가장 처음 실행되는 단계로, 컴포넌트 초기화 단계이다. 즉, 컴포넌트들이 DOM에 추가되기 전 단계이다. 서버렌더링에서도 지원되는 훅이기 때문에 클라이언트 단과 서버 렌더링 단 모두에서 처리해야한다면 이 단계에서 수행하면 되겠다. 하지만 아직 컴포넌트가 DOM에 추가되기 전이기 때문에 DOM에 접근하거나 this.$el를 사용할 수 없다.

이 단계에는 또 2가지 훅이있는데, beforeCreate 훅과 Created 훅이다.

  1. beforeCreate 모든 훅 중에 가장 먼저 실행되는 훅으로, 아직 data와 events(vm.$on, vm.$once, vm.$off, vm.$emit)가 세팅되지 않은 시점이다. 따라서 이때 데이터나 이벤트에 접근하려고 하면 에러를 뿜뿜하게 된다.
    <script>
    export default {
      beforeCreate() {
        console.log('At this point, events and lifecycle have been initialized.')
      }
    }
    </script>
  1. created created 훅에서는 이제 data와 events가 활성화되어 접근할 수 있다. 하지만, 여전히 템플릿과 가상돔은 마운트 및 렌더링되지 않은 상태이다.
      <template>
        <div ref="example-element"></div>
      </template>

      <script>
      export default {
        data() {
          return {
            property: 'Example property.'
          }
        },

        computed: {
          propertyComputed() {
            return this.property
          }
        },

        created() {
          console.log('At this point, this.property is now reactive and propertyComputed will update.')
          this.property = 'Example property updated.'
        }
      }
      </script>

위 코드 예시를 살펴보면, propertyExample property로 저장될 것이다. created 훅이 실행될때, 콘솔에 로그로 'At this point, this.property is now reactive and propertyComputed will update.'라는 메세지가 찍히게 되고, 그 다음 propertyExample property updated.로 변경된다. 따라서, 이후 라이프 사이클에서 ``에는 Example property updated.가 찍히게 된다.

Mounting

Mounting 단계는 초기 렌더링 직전에 컴포넌트에 직접 접근할 수 있는 단계로 DOM 삽입 단계이다. 하지만 서버렌더링에서는 지원하지 않는다.

초기 랜더링 직전에 DOM을 변경하고자 한다면 이 단계를 활용할 수 있지만 컴포넌트 초기에 세팅되어야 할 데이터 패치의 경우 created 단계에서 마치는 것이 좋다.

이 단계도 마찬가지로 2가지 단계로 나뉘는데, beforeMount와 mounted 단계이다.

  1. beforeMount beforeMount 훅은 템플릿과 렌더 함수들이 컴파일된 후, 첫 렌더링이 일어나기 직전에 실행된다. 대부분의 경우에 사용하지 않는 것이 좋다. 그리고 서버사이드 렌더링시에는 호출되지 않는다.
   <script>
    export default {
      beforeMount() {
      console.log(`this.$el doesn't exist yet, but it will soon!`)
      }
    } 
    </script>
  1. mounted mounted 훅에서는 컴포넌트, 템플릿, 렌더링된 DOM에 접근할 수 있다. mounted는 vue 라이브러리 외의 non-vue 라이브러리를 통합하기 위해 DOM을 수정해야할 때 사용한다.
   <script>
    export default {
      mounted() {
      console.log(this.$el.textContent) // can use $el
    
    }
    }
    </script>

mounted 훅에서 가장 주의해아할 점은, 서론에서 얘기한 것처럼 VUE는 부모와 자식관계의 컴포넌트가 기존의 로직대로 mounted되지 않는 다는 점이다. 즉, 부모의 mounted 훅이 자식의 mounted 훅보다 먼저 실행되지 않고, 오히려 자식의 mounted가 모두 완료된 후에 부모의 mounted 훅이 실행된다.

아래는 부모와 자식간 mounted 실행 다이어그램이다.

위 그림에서 알 수 있듯이 created 훅은 부모 -> 자식의 순이지만 mounted는 자식 -> 부모 순이며, 자식의 mounted 단계가 완료되어야 부모의 mounted 가 실행된다.

그러나 자식 컴포넌트가 서버에서 비동기로 데이터를 받아오는 경우처럼, 부모의 mounted 훅이 실행되었다고해서 모든 자식 컴포넌트가 mounted된 상태라는 것을 보장하진 않는다. 따라서 이때는 this.$nextTick을 사용하여 모든 화면이 렌더링 된 후에 실행하도록 할 수 있다.

Updating

컴포넌트에서 사용되는 반응형 속성들이 변경되거나 어떤 이유로 재 렌더링이 발생되면 실행된다. 디버깅이나 프로파일링 등을 위해 컴포넌트 재 렌더링 시점을 알고 싶을때 사용하면 된다. 하지만, 컴포넌트의 반응형 속성이 언제 변경되는지 알아야하는 경우 사용하지 않는 것이 좋으며, 그때는 computed속성이나 watchers를 쓰는것이 좋다.이 단계는 서버렌더링에서는 호출되지 않는다.

이 단계도 beforeUpdate와 updated 두가지로 나뉜다.

  1. beforeUpdate 이 훅은 컴포넌트의 데이터가 변하여 업데이트 사이클이 시작될 때 실행된다. 정확히는 DOM이 재 렌더링되고 패치되기 직전에 실행된다. 재 렌더링 전의 새 상태의 데이터를 얻고자 할 때 사용한다. 이 변경으로 인한 재 렌더링은 트리거되지 않는다.
  <template>
  <div ref="example-element"></div>
  </template>

  <script>
  export default {
    data() {
      return {
        counter: 0
      }
    },

    created() {
      setInterval(() => {
        this.counter++
      }, 1000)
    },

    beforeUpdate() {
      console.log(`At this point, Virtual DOM has not re-rendered or patched yet.`)
      // Logs the counter value every second, before the DOM updates.
      console.log(this.counter)
    }
  }
  </script>

위 예시 코드를 살펴보자. 먼저 counter는 0으로 저장된다. created훅이 실행될 때, counter는 1000ms당 1씩 증가하게 된다. beforeUpdate훅이 실행될 때, At this point, Virtual DOM has not re-rendered or patched yet. 메세지가 로그로 출력되고 counter의 숫자가 출력된다.

  1. updated

updated훅은 컴포넌트의 데이터 변경 후 재 렌터링이 일어난 후에 실행된다. DOM 업데이트가 완료된 상태이므로 DOM에 종속적인 연산을 실행할 수 있다. 단, 주의할 점은 여기서 data를 변경하면 무한루프에 빠질 수 있다. 또한 모든 자식컴포넌트의 재 렌더링 상태를 보장하진 않는다.

  <template>
  <div ref="example-element"></div>
  </template>

  <script>
  export default {
    data() {
      return {
        counter: 0
      }
    },

    created() {
      setInterval(() => {
        this.counter++
      }, 1000)
    },

    updated() {
      console.log(`At this point, Virtual DOM has re-rendered and patched.`)
      // Fired every second, should always be true
      console.log(+this.$refs['example-element'].textContent === this.counter)
      }
    }
    </script>

beforeUpdate의 예시 코드와 마찬가지로 counter는 처음엔 0으로 저장된다. created 훅이 실행되면 1초마다 counter는 1씩 증가한다. updated훅이 실행되면 로그로 메세지가 출력되고 랜더링된 counter값과 현재 counter 값이 같을 때 boolean 값이 출력된다.

Destruction

마지막인 해체단계이다. 이 단계는 구성요소가 해체되고 DOM에서 제거될 때 실행되는데, 이 단계의 훅을 사용하여 컴포넌트 요소가 해체되었을 때 cleanup이나 분석 내용 전송 등을 수행할 수 있다.

  1. beforeDestory 이 훅은 뷰 인스턴트가 제거되기 직전에 호출된다. 컴포넌트는 원래의 모습과 기능을 그대로 가지고 있는 상태이다. 이 훅은 이벤트 리스너를 제거하거나 reactive subscription을 제거하고자 할 때 주로 사용된다. 서버렌더링 시에는 호출되지 않는다.

  2. destroyed 이 훅은 뷰 인스턴트가 제거된 후에 호출된다. Vue 인스턴트의 모든 바인딩이 해제되고 모든 이벤트 리스너가 제거되며 하위 Vue 인스턴스도 제거된다. 서버 렌더링 시엔 호출되지 않는다.

errorCaptured

자손 컴퍼넌트로부터의 에러가 캡처되었을 때에 불린다. 오류를 트리거 한 컴포넌트 인스턴스 및 오류가 캡처된 위치에 대한 정보가 들어있는 문자열의 세 가지 전달인자를 받는다. 훅은 false를 반환하여 오류가 더 전파되지 않도록 할 수 있다.

이 훅에서는 컴포넌트 상태를 수정할 수 있으나 오류가 캡처되었을 때 다른 내용을 더이상 실행시키지 않는 조건부 템플릿을 사용하거나 렌더링 기능을 사용하는 것이 중요하다. 그렇지 않으면 컴포넌트가 무한 렌더링 루프에 빠질 수 있다.

정리를 마치며…

Vue.js의 라이프 사이클을 정리해보았다. 개발 당시 가장 많이 사용한 훅은 아무래도 mounted 훅 인듯 하다. 다시 정리해보며 느끼지만, 라이프 사이클을 완벽하게 이해하고 개발하면 훨씬 더 동작이 매끄럽고 깔끔한 프론트가 되지 않았을까 싶다.

Vue.js의 초보자들에게 도움이 되는 포스팅이었길 바래본다. :)

참고자료 및 이미지 출처

https://kr.vuejs.org/v2/api/#updated https://www.digitalocean.com/community/tutorials/vuejs-component-lifecycle