고급 Vue 앱의 장점 : 컴포넌트 구현하기 편리
재사용하기 좋은 형태로 구현
javascript 언어에서 날짜를 다룰 때, 편리한 오픈소스 라이브러리가 moment.js
➡️ javascript 표준 날짜 라이브러리
CDN 서비스 활용하여 moment 라이브러리 사용하려면 아래 태그 필요
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/moment.min.js"></script>
하지만, Vue 프로젝트에서는 CDN 서비스를 사용하지 않고, 라이브러리 파일을 미리 프로젝트 폴더 아래 node_modules 폴더에 다운로드하고, 트랜스파일하고 빌드하여 배포 파일 생성
npm install --save moment
프로젝트를 압축하여 보관할 때, node_modules 폴더를 삭제하고 압축하는 것이 좋음(파일이 너무 큼)
다시 실행하여 사용하고 싶을 땐 npm install
하면 언제든지 다운로드 됨(이미 필요한 모듈들이 기록되어있기 때문)
src/MyClock.vue
<template>
<div>{{ time }}</div>
</template>
<script>
import moment from "moment"; //moment 라이브러리 임포트
export default {
name: "MyClock", // 컴포넌트 이름
data() {
return { time: null }; //time 초기 값 null
},
mounted() {
let callback = () => this.time = moment().format("HH:mm:ss");
callback();
setInterval(callback, 1000);
}
}
</script>
<style scoped>
div { display: inline-block; font-size: 15pt; padding:3px 12px; border: 1px solid gray; }
</style>
컴포넌트가 생성되고, 컴포넌트의 속성들도 생성된 후, 화면에 그려지기 전에 이 메소드가 한 번 자동으로 호출.
컴포넌트 초기화를 구현하기 적당
src/App..vue
<template>
<div id="app">
<h1>시계</h1>
<MyClock></MyClock>
</div>
</template>
<script>
import MyClock from './MyClock.vue'
export default {
name: "App",
components: { MyClock } //사용할 컴포넌트 미리 선언
}
</script>
<style scoped>
div#app { padding: 0 30px 30px 30px; margin: 30px auto; max-width: 400px;
border: 1px solid #ccc; box-shadow: 3px 3px 3px #aaa; }
</style>
웹브라우저가 HTML 텍스트 문서를 읽고 분석(parsing)하여 HTML 문서 객체를 생성 ▶︎ 이 객체가 웹브라우저 창에 그려짐(c++)
HTML 문서 객체는 HTML 태그 객체 포함 ▶︎ 이 객체들이 웹브라우저 내부 메모리(RAM)에 들어있음
Vue 컴포넌트의 <template> 태그 부분에 정의된 HTML 태그들을 읽고 분석하여 HTML 문서 객체와 태그 객체 생성 ➡️ 이 작업은 Vue 엔진이 수행(javascript), 웹브라우저 창에 그려지지 않음
Virtual DOM 객체들이 웹브라우저 창에 그려지려면, 똑같은 구조로 웹브라우저 내부에 DOM 객체를 생성해야함
컴포넌트의 속성 값이 변경되면, 그 속성과 연결된 태그 객체의 내용도 즉시 변경되어야 함
Vue 엔진은 Virtual DOM 객체들만 대상으로 변경 작업 수행 ▶︎ DOM 객체들을 아직 변경X
변경된 Virtual DOM 객체들과 동일한 구조로, 웹브라우저 내부의 DOM 객체를 생성하고 수정하는 작업
이 작업을 마쳐야 웹브라우저 창의 내용도 변경
: 컴포넌트의 생명주기(life cycle)의 특정 시점에 자동으로 호출되는 메소드
beforeMount 메소드
: 컴포넌트 객체가 생성, 속성들도 만들어지고, Virtual DOM 객체도 만들어진 후 자동 호출
⭐️mounted 메소드(initial render)
: 컴포넌트 객체 생성, 컴포넌트 속성 생성, Virtual DOM 객체 생성된 후, DOM 객체와 Virtual DOM 객체가 연결(mounted)된 후(실제 DOM이 처음 만들어짐) 자동으로 호출, 최초 한번 호출 ▶︎ 초기화 작업 구현하기 적당
beforeUpdate 메소드
: 렌더링 작업 후, 업데이트 작업 직전 호출, 화면이 변화할 때마다 호출
화면이 변화하기 직전에 뭔가 추가로 해야할 일이 있다면 이 곳에 구현
⭐️updated
: 업데이트 작업 직후에 호출, 화면이 변화할 때마다 호출
화면이 변화하기 직전에 뭔가 추가로 해야할 일이 있다면 이 곳에 구현
beforeUnmount
: 컴포넌트가 파괴되기 직전에 호출
⭐️unmounted
: 컴포넌트가 파괴된 직후 호출
초기화 작업 : mounted 메소드에 구현
마무리 작업 : beforeUnmounted 메소드에 구현(파괴되기 전)
<template>
<div>{{ time }}</div>
</template>
<script>
import moment from "moment";
export default {
name: "MyClock",
data() {
return { time: null };
},
mounted() {
let callback = () => {
this.time = moment().format("HH:mm:ss");
console.log(this.time);
}
callback();
setInterval(callback, 1000);
}
}
</script>
<style scoped>
div { display: inline-block; font-size: 15pt; padding:3px 12px; border: 1px solid gray; }
</style>
callback 함수가 호출될 때마다 console에 현재 시각을 출력하는 코드 추가
<template>
<div id="app">
<h1>시계</h1>
<MyClock v-if="showTimer"></MyClock>
<div>
<button type="button" @click="showTimer = true"
:disabled="showTimer">시계 보이기</button>
<button type="button" @click="showTimer = false"
:disabled="!showTimer">시계 감추기</button>
</div>
</div>
</template>
<script>
import MyClock from './MyClock.vue'
export default {
name: "App",
data: function() {
return {
showTimer: true
}
},
components: { MyClock }
}
</script>
<style scoped>
div#app { padding: 0 30px 30px 30px; margin: 30px auto; max-width: 400px;
border: 1px solid #ccc; box-shadow: 3px 3px 3px #aaa; }
button { padding: 0.5em 1.5em; margin: 20px 5px 0 0; }
</style>
🚨 '시계 감추기' 버튼을 누르면 MyClock 컴포넌트가 메모리애서 제거되어야 하는데, 콜백함수가 계속 호출중
▶︎ '시계 보이기' 버튼을 누르면 MyClock 컴포넌트가 새로 생성되어서 컴포넌트 객체가 2개가 됨 ➡️ memory leak
<template>
<div>{{ time }}</div>
</template>
<script>
import moment from "moment";
export default {
name: "MyClock",
data() {
return { time: null };
},
mounted() {
let callback = () => {
this.time = moment().format("HH:mm:ss");
console.log(this.time);
}
callback();
this.timerId = setInterval(callback, 1000);//id 받아오기
},
beforeUnmount() {
clearInterval(this.timerId); //setInterval 종료
}
}
</script>
<style scoped>
div { display: inline-block; font-size: 15pt; padding:3px 12px; border: 1px solid gray; }
</style>
➡️ 콜백함수 등록이 취소되지 않으면, 반복 호출 엔진이 콜백함수 계속 참조
콜백함수 본문에서 컴포넌트의 time 속성 사용 ▶︎ 콜백함수가 컴포넌트 참조 ▶︎ 가비지 콜렉션 X
따라서 다시 mounted 되기 전에 콜백함수 등록을 취소해야함(beforeUnmounted)
수정될 가능성이 있는 부분을 컴포넌트 외부로 추출, 수정될 가능성이 거의 없는 부분만 컴포넌트 내부에 구현
<template>
<div>{{ time }}</div>
</template>
<script>
import moment from "moment";
export default {
name: "MyClock",
props: {
format: { type: String, default: "HH:mm:ss" }
},
data() {
return { time: null };
},
mounted() {
let callback = () => this.time = moment().format(this.format)
callback();
this.timerId = setInterval(callback, 1000);
},
beforeUnmount() {
clearInterval(this.timerId);
}
}
</script>
<style scoped>
div { display: inline-block; }
</style>
<template>
<div id="app">
<h1>clock</h1>
<MyClock v-if="showTimer" class="clockStyle1" format="YYYY-MM-DD HH:mm:ss"></MyClock> //format props 값 전달
<MyClock v-if="showTimer" class="clockStyle2"></MyClock>
<div>
<button type="button" @click="showTimer = true"
:disabled="showTimer">시계 보이기</button>
<button type="button" @click="showTimer = false"
:disabled="!showTimer">시계 감추기</button>
</div>
</div>
</template>
<script>
import MyClock from './MyClock.vue'
export default {
name: "App",
data() {
return {
showTimer: true
}
},
components: { MyClock }
}
</script>
<style scoped>
div#app { padding: 0 30px 30px 30px; margin: 30px auto; max-width: 400px;
border: 1px solid #ccc; box-shadow: 3px 3px 3px #aaa; }
button { padding: 0.5em 1.5em; margin: 20px 5px 0 0; }
.clockStyle1, .clockStyle2 { font-size: 15pt; border: 1px solid gray; }
.clockStyle1 { padding: 3px 12px; margin-right: 5px; background-color: #ffa; }
.clockStyle2 { padding: 3px 30px; background-color: #bfb; }
</style>