Development Artist

[웹 서비스 A-Z][Vuejs] #5 Life Cycle 본문

Project_Personal

[웹 서비스 A-Z][Vuejs] #5 Life Cycle

JMcunst 2023. 3. 1. 17:22
728x90
반응형

Life Cycle

이전 시간까지 우리는 Vue의 디자인패턴과 그에 따라 어떻게 동작하는지 살펴 보았는데요, 이번 시간에는 Vue의 라이프 사이클과 그에 따라 <script> 단의 처리를 살펴보겠습니다.

공식 홈페이지에서는 다음과 같이 라이프사이클을 보여주고 있습니다. 왼쪽의 빨간색 상자별로 단계를 나누면 되고 이들을 훅(Hook)이라고 부릅니다.

Vue 인스턴스는 생성(create)되고, DOM에 삽입(mount)되고, 업데이트(update)되며, 삭제(destroy) 됩니다.

beforeCreate : 인스턴스가 초기화 될때 즉시 호출 됩니다. [data()와 computed 옵션 이전, 그리고 props resolution 이후]

created : 인스턴스에서 모든 state-related 옵션이 처리된 후 호출 됩니다. 해당 훅이 완료되면, 반응형 데이터, computed된 속성값, methods, watcher가 세팅됩니다. date property가 setter와 getter로 정의되기 때문에 created 이후 data에 접근할 수 있습니다. $el 과 같은 속성은 아직입니다.

주로, 서버로 데이터를 요청합니다. 서버로 부터 받은 데이터를 컴포넌트 상태 값에 대입해 줄 수 있습니다.

beforeMount : mounted 전 호출되는 훅입니다. 반응형 상태들은 세팅완료 되었지만, DOM 노드는 아직 생성되지 않았습니다. 막 DOM 렌더 이펙트를 실행하려고 하고 있는 상태입니다. 이 훅은 서버 사이드 렌더링 동안 호출되지 않습니다.

mounted : 컴포넌트가 마운트 된 후 호출 됩니다. 모든 하위 컴포넌트가 마운트되고, 실제 브라우저 DOM 트리가 생성되어 상위 컨테이너에 삽입됩니다. 즉, 가상 DOM이 실제 DOM에 부착되고 난 이후 실행이 됩니다. this.$el 및 모든 요소에 접근이 가능합니다.

일반적으로 부모 자식 컴포넌트 간의 mounted는 위와 같은데요, 부모의 mounted 훅은 항상 자식 컴포넌트 mounted 훅 이후에 발생합니다. 하지만, 자식 컴포넌트가 서버에서 비동기로 데이터를 받아오는 경우처럼, 부모의 mounted 훅이 모든 자식 컴포넌트가 마운트 된 상태를 보장하지 않습니다. 이럴 때는 this.$netxTick을 사용해서 마운트 상태를 보장할 수 있습니다.

이 훅은 서버 사이드 렌더링 동안 호출되지 않습니다.

beforeUpdate : updated 훅 이전에 호출 됩니다.

updated : 가상 DOM을 렌더링 하고 실제 DOM이 변경된 후 호출 합니다. 컴포넌트의 data 값이 변해서 DOM에 변화를 적용시켜야 할 때 사용 됩니다.

하지만, 이 훅은 무한 루프를 일으킬 수 있으므로 data를 직접 바꾸는 것은 권장하지 않습니다.

💡 Vue 2 에서는 beforeDestroy와 destroyed를 제공합니다.

자, 그럼 이제 라이프 사이클은 알겠는데, 어떻게 사용한다는 걸까요? 왜 이걸 알아야 하지? 할 수 있습니다.

지금부터 간단한 예시와 함께 테스트를 진행해보겠습니다.

일단 vue-router를 설치하겠습니다. 설치 후 package.json 파일에서 dependencies에 vue-router를 버전과 함께 확인이 되면 성공적으로 설치가 된 것입니다.

# Vue 2와 호환되는 버전의 vue-router를 설치해야 합니다
npm i vue-router@3.5.3 --save

src 아래에 views 폴더를 만듭니다. 그리고 HomePage.vue와 AboutPage.vue를 만듭니다.

// src/views/HomePage.vue
<template>
    <div>
        <router-link to="/">Home</router-link> |
        <router-link to="/about">About</router-link>
        <h3>이곳은 Home Page.</h3>
        <p>COUNT:{{cnt}}, ONE:{{data1}}, TWO:{{data2}}, THREE:{{data3}}</p>
        <button v-on:click="dataupdate()">click</button>
    </div>
</template>
<script>
export default {
    name: 'HomePage',
    props: {
        msg: String
    },
    data() {
        return {
            data1: 0,
            data2: 0,
            data3: 0,
            cnt: 1
        }
    },
    computed: {
        titleComputed() {
            console.log('I change when this.property changes.')
            return this.property
        }
    },
    beforeCreate() {
        console.log('HomePage beforeCreate:', this.cnt, this.data1, this.data2, this.data3);
    },
    created() {
        console.log('HomePage created:', this.cnt, this.data1, this.data2, this.data3);
    },
    beforeMount() {
        console.log('HomePage beforeMount:', this.cnt, this.data1, this.data2, this.data3);
    },
    mounted() {
        console.log('HomePage mounted:', this.cnt, this.data1, this.data2, this.data3);
    },
    beforeUpdate() {
        console.log('HomePage beforeUpdate:', this.cnt, this.data1, this.data2, this.data3);
    },
    updated() {
        console.log('HomePage updated:', this.cnt, this.data1, this.data2, this.data3);
    },
    beforeDestroy() {
        console.log('HomePage beforeDestroy:', this.cnt, this.data1, this.data2, this.data3);
    },
    destroyed() {
        console.log('HomePage destroyed:', this.cnt, this.data1, this.data2, this.data3);
    },
    methods: {
        dataupdate() {
            if (this.cnt % 3 == 1) {
                this.data1 += 1;
            } else if (this.cnt % 3 == 2) {
                this.data2 += 1;
            } else {
                this.data3 += 1;
            }
            this.cnt += 1;
        }
    }
};
</script>
// src/views/AboutPage.vue
<template>
    <div>
        <router-link to="/">Home</router-link> |
        <router-link to="/about">About</router-link>
        <h3>이곳은 About Page.</h3>
        <p>COUNT:{{cnt}}, ONE:{{data1}}, TWO:{{data2}}, THREE:{{data3}}</p>
        <button v-on:click="dataupdate()">click</button>
    </div>
</template>
<script>
export default {
    name: 'AboutPage',
    props: {
        msg: String
    },
    data() {
        return {
            data1: 0,
            data2: 0,
            data3: 0,
            cnt: 1
        }
    },
    computed: {
        titleComputed() {
            console.log('I change when this.property changes.')
            return this.property
        }
    },
    beforeCreate() {
        console.log('AboutPage beforeCreate:', this.cnt, this.data1, this.data2, this.data3);
    },
    created() {
        console.log('AboutPage created:', this.cnt, this.data1, this.data2, this.data3);
    },
    beforeMount() {
        console.log('AboutPage beforeMount:', this.cnt, this.data1, this.data2, this.data3);
    },
    mounted() {
        console.log('AboutPage mounted:', this.cnt, this.data1, this.data2, this.data3);
    },
    beforeUpdate() {
        console.log('AboutPage beforeUpdate:', this.cnt, this.data1, this.data2, this.data3);
    },
    updated() {
        console.log('AboutPage updated:', this.cnt, this.data1, this.data2, this.data3);
    },
    beforeDestroy() {
        console.log('AboutPage beforeDestroy:', this.cnt, this.data1, this.data2, this.data3);
    },
    destroyed() {
        console.log('AboutPage destroyed:', this.cnt, this.data1, this.data2, this.data3);
    },
    methods: {
        dataupdate() {
            if (this.cnt % 3 == 1) {
                this.data1 += 1;
            } else if (this.cnt % 3 == 2) {
                this.data2 += 1;
            } else {
                this.data3 += 1;
            }
            this.cnt += 1;
        }
    }
};
</script>

그 다음, src 아래에 router.js 파일을 생성해줍니다. HomePage는 / 경로로, AboutPage는 /about 경로로 사용하기 위한 설정을 담고 있습니다.

// /src/router.js
import Vue from "vue";
import VueRouter from "vue-router";
import Home from "./views/HomePage";
import About from "./views/AboutPage";

Vue.use(VueRouter);

const router = new VueRouter({
    mode: "history",
    routes: [
        {
            path: "/",
            component: Home,
        },
        {
            path: "/about",
            component: About,
        },
    ],
});
export default router;

라우터를 설정한 것을 적용시켜야 합니다. main.js에서 Vue 인스턴스를 만들때 넣어주면 됩니다.

// src/main.js
import Vue from 'vue'
import App from './App.vue'  
import router from './router'  // 추가 부분

Vue.config.productionTip = false

new Vue({
  router,  // 추가 부분
  render: h => h(App),
}).$mount('#app')

그리고 현재 App.vue에서 HelloWorld 컴포넌트를 사용하고 있는데요, 이제 HelloWorld를 빼고, HomePage와 AboutPage를 사용합니다. 따로 HomePage와 AboutPage를 import 하지 않더라도, <router-view> 태그를 사용하면, router.js에서 설정한 path대로 보여줄 수 있습니다. 또한, default path가 / 이기 때문에, App.vue는 HomePage.vue를 보여주게 됩니다.

// src/App.vue
<template>
  <div id="app">
    <router-view></router-view>
  </div>
</template>

<script>
// import HelloWorld from './components/HelloWorld.vue'

export default {
  name: 'App',
  components: {
    // HelloWorld
  }
}
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

이제 npm run serve 를 해봅시다.

그리고 F12를 눌러 Console 창을 띄워 봅니다. 다음과 같이 말이죠.

화면이 보이는 시점에 beforeCreate, created, beforeMount, mounted가 수행된 것을 확인할 수 있습니다.

그리고 click을 누르게 되면 count 가 1 올라가고, ONE, TWO, THREE가 차례대로 1씩 증가하게 끔 동작하는 함수를 실행하게 됩니다. data가 변화가 되어 DOM을 새로 쓰고 그려야 하기 때문에 update가 동작하는 것을 확인할 수 있습니다.

그리고 About을 누르게 되면, /about으로 라우팅이 되면서 AboutPage 컴포넌트를 생성하고 마운트하게 됩니다. 그리고 HomePage의 경우 destroy가 되면서 종료가 되는 것을 확인할 수 있습니다.

 

이렇게 Vue의 라이프 사이클에 대해 알아보는 시간을 가졌습니다. 다음 장에서는 부모 자식간 데이터 전달에 대해 설명하도록 하겠습니다.

 

감사합니다.

Additional

Vue의 렌더링을 조금 더 설명하고자 합니다. 위의 라이프 사이클에서 DOM에 대한 얘기와 가상 DOM, 실제 DOM에 대해 언급하고 있는데요,

브라우저에는 코드를 그래픽화 해주는 렌더링 엔진이 있습니다. 오픈소스인 webkit 렌더링 엔진은 Chrome과 Safari의 렌더링 엔진으로 사용되고 있습니다.

또한, Gecko 엔진이 있는데 Firefox가 사용하고 있습니다.

왼쪽의 노란색 부분에서 렌더링 엔진은 HTML과 CSS파일을 가져와 파싱하여 각각 DOM Tree(Content Model)과 Style Rules를 만듭니다. 이 2개를 Attachment(Frame Constructor) 과정을 통해 합치게 되고 Render Tree(Frame Tree)를 만듭니다. 이것을 가지고 Painting을 하게 되고 사용자에게 보여지게 됩니다.

여기서 DOM Tree(Content Model)가 실제 DOM 입니다.

 

그렇다면, Vue에서 사용되는 Virtual DOM은 무엇인가요?

 

Vue 프로젝트를 시작하면 cli-plugin-bable 이라는 패키지가 설치 되는데요, 이 녀석이 template과 script를 컴파일해서 Virtual DOM을 만듭니다. 빌드시 생성되는 dist/ 폴더에 js/ 폴더와 /css 폴더가 Virtual DOM의 결과물인 것이죠.

위의 브라우저 렌더링 플로우에서 Vue는 HTML과 StyleSheets 대신 DOM 처리된 객체(Virtual DOM)를 브라우저에 보내고 바로 Attachment(Frame Constructor)가 진행되게 합니다.

 

따라서 렌더링 속도를 빠르게 할 수 있게 되죠.

728x90
반응형
Comments