[프론트엔드_개발] Clock 컴포넌트

혯승·2023년 5월 15일
0

프론트엔드_개발

목록 보기
5/9
post-thumbnail

Clock 컴포넌트

고급 Vue 앱의 장점 : 컴포넌트 구현하기 편리
재사용하기 좋은 형태로 구현

moment.js

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 하면 언제든지 다운로드 됨(이미 필요한 모듈들이 기록되어있기 때문)

💻clock2 프로젝트

MyClock.vue 생성

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>

mouted()

컴포넌트가 생성되고, 컴포넌트의 속성들도 생성된 후, 화면에 그려지기 전에 이 메소드가 한 번 자동으로 호출.
컴포넌트 초기화를 구현하기 적당

App.vue 수정

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>

💦메모리 누출

1) Virtual DOM vs 실제 DOM

DOM(Document Object Model)

웹브라우저가 HTML 텍스트 문서를 읽고 분석(parsing)하여 HTML 문서 객체를 생성 ▶︎ 이 객체가 웹브라우저 창에 그려짐(c++)

HTML 문서 객체는 HTML 태그 객체 포함 ▶︎ 이 객체들이 웹브라우저 내부 메모리(RAM)에 들어있음

Virtual DOM

Vue 컴포넌트의 <template> 태그 부분에 정의된 HTML 태그들을 읽고 분석하여 HTML 문서 객체와 태그 객체 생성 ➡️ 이 작업은 Vue 엔진이 수행(javascript), 웹브라우저 창에 그려지지 않음

Virtual DOM 객체들이 웹브라우저 창에 그려지려면, 똑같은 구조로 웹브라우저 내부에 DOM 객체를 생성해야함

랜더링(rendering)

컴포넌트의 속성 값이 변경되면, 그 속성과 연결된 태그 객체의 내용도 즉시 변경되어야 함

Vue 엔진은 Virtual DOM 객체들만 대상으로 변경 작업 수행 ▶︎ DOM 객체들을 아직 변경X

업데이트(update)

변경된 Virtual DOM 객체들과 동일한 구조로, 웹브라우저 내부의 DOM 객체를 생성하고 수정하는 작업
이 작업을 마쳐야 웹브라우저 창의 내용도 변경

  • 랜더링 작업과 업데이트 작업을 분리한 이유 : 더 빠르기 때문에
    javascript 코드에서 웹브라우저 내부의 DOM 객체를 대상으로 랜더링 작업을 하는 것 보다, Virtual DOM 객체를 대상으로 랜더링하는 것이 훨씬 빠름.
    업데이트는 상대적으로 간단해서 금방 끝남

2) life cycle hook 메소드

: 컴포넌트의 생명주기(life cycle)의 특정 시점에 자동으로 호출되는 메소드

  • beforeMount 메소드
    : 컴포넌트 객체가 생성, 속성들도 만들어지고, Virtual DOM 객체도 만들어진 후 자동 호출

  • ⭐️mounted 메소드(initial render)
    : 컴포넌트 객체 생성, 컴포넌트 속성 생성, Virtual DOM 객체 생성된 후, DOM 객체와 Virtual DOM 객체가 연결(mounted)된 후(실제 DOM이 처음 만들어짐) 자동으로 호출, 최초 한번 호출 ▶︎ 초기화 작업 구현하기 적당

  • beforeUpdate 메소드
    : 렌더링 작업 후, 업데이트 작업 직전 호출, 화면이 변화할 때마다 호출
    화면이 변화하기 직전에 뭔가 추가로 해야할 일이 있다면 이 곳에 구현

  • ⭐️updated
    : 업데이트 작업 직후에 호출, 화면이 변화할 때마다 호출
    화면이 변화하기 직전에 뭔가 추가로 해야할 일이 있다면 이 곳에 구현

  • beforeUnmount
    : 컴포넌트가 파괴되기 직전에 호출

  • ⭐️unmounted
    : 컴포넌트가 파괴된 직후 호출

초기화 작업 : mounted 메소드에 구현
마무리 작업 : beforeUnmounted 메소드에 구현(파괴되기 전)

3) 메모리 누출 확인

MyClock.vue 수정

<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에 현재 시각을 출력하는 코드 추가

App.vue 수정

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

v-if vs v-show

  • v-if : 보이는 상태가 변경될 때마다, 해당 태그 객체를 생성하고 파괴(unmounted)
  • v-show : 웹브라우저 창에 표시되지 않음

🚨 '시계 감추기' 버튼을 누르면 MyClock 컴포넌트가 메모리애서 제거되어야 하는데, 콜백함수가 계속 호출중


▶︎ '시계 보이기' 버튼을 누르면 MyClock 컴포넌트가 새로 생성되어서 컴포넌트 객체가 2개가 됨 ➡️ memory leak

4) 메모리 누출 해결

MyClock.vue 수정

<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)

🆙재사용성 향상

수정될 가능성이 있는 부분을 컴포넌트 외부로 추출, 수정될 가능성이 거의 없는 부분만 컴포넌트 내부에 구현

MyClock.vue 수정

<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>
  • 시각 출력 포멧
    수정될 가능성이 있는 시각 출력 포멧을 props로 구현
    값을 변경하려면 새 포멧 문자열을 props 값으로 전달하면됨

App.vue 수정

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

0개의 댓글

관련 채용 정보