본문 바로가기

Server Programming/Spring Boot 2 Full-Stack Programming

[작업 관리 애플리케이션 개발] 2. Vue.js 2

반응형

목차

  1. 기본 개념
    1. Vue 인스턴스
    2. 컴포넌트
    3. Vue 인스턴스 라이프 사이클
    4. 지시자
    5. 필터
    6. 믹스인
    7. 플러그인
  2. 반응형 시스템 동작 방식
    1. 내부 구현
    2. 로직과 설계

기본 개념

: 작은 규모에서 대규모로 성장시킬 수 있는 점진적인 프레임워크 Vue.js

 

  1. Vue 인스턴스
  2. 컴포넌트
  3. Vue 인스턴스 라이프 사이클
  4. 지시자
  5. 필터
  6. 믹스인
  7. 플러그인

 


싱글 페이지 애플리케이션 SPA인 메시지 앱

  1. 메시지 추가하기
  2. 메시지 리스트 보기
  3. 메시지 삭제하기
  4. 특정 조건에서 추가 기능을 자동으로 비활성화하기

 

목차

  1. Vue인스턴스를 이용해 index.html 생성
  2. 컴포넌트를 이용해 메시지 리스트 렌더링과 메시지 삭제 구현
  3. 지시자를 이용해 페이지 열린 뒤 폼의 입력 항목에 초점 
  4. 필터를 이용해 메시지 앱에 보기 좋은 날짜와 시간 형식 제공
  5. 훅 함수에서 인스턴스 이름과 단계를 출력하기 위해 믹스인 이용
  6. 플러그인을 이용해 인스턴스의 라이프 사이클 추적

 


Vue 인스턴스

  1. 루트 Vue 인스턴스
  2. 컴포넌트 인스턴스

루트 인스턴스 생성

: Vue 함수를 이용

new Vue({/* options */});

-options 객체에 애플리케이션 기술해 이 객체를 이용해 Vue 인스턴스 초기화

 

(1) index.html 작성

<!DOCTYPE html>
<html>
<head>
  <title>Messages App</title>
</head>
<body>
  <div id="app" v-cloak>
  <script src="https://unpkg.com/vue@2.5.13/dist/vue.js"></script>
  <script>
    let vm = new Vue({
      el: '#app',
    });
   </script>
</body>
</html>

 

(2) 데이터 모델 정의

-객체 리터럴을 사용해 data프로퍼티 값으로 일반객체 객체 이용

-message는 배열, newMessagesms는 문자열로 초깃값 지정

-초깃값을 지정해주면, 프로퍼티를 반응형으로 만들어준다.

  <script>
    let vm = new Vue({
      el: '#app',
      data: {
        messages: [],
        newMessage: ''
      }
    });
  </script>

일반 객체를 반환하는 함수를 이용할 수도 있다.

data(){
	return{
    	messages:[],
        newMessage:''
    }
}

 

Vue.js가 함수를 이용해 새로운 컴포넌트에 대한 새로운 데이터 모델을 생성하므로 데이터 구조 정의시 함수를 사용해야만 한다.

-일반 객체를 사용하면 컴포넌트의 모든 인스턴스가 같은 data 객체를 공유하므로, 루트 Vue 인스턴스 이외에는 일반 객체를 사용하면 안된다.

 

(3) data 객체로 메시지를 표시하고 추가하는 템플릿 추가

템플릿 추가 방법

  1. options 객체의 template 프로퍼티를 활용해 인라인 템플릿 문자열 추가
  2. 템플릿을 부착 지점인 <div><id="app"></div>에 직접 삽입
  3. 템플릿 마크업을 <script type="x-template" id="tmplApp"> 태크 내에 넣어 options객체의 template 프로퍼티 값으로 "#tmplapp"을 넣는다.

템플릿 직접 삽입 순서

  1. Vue에 내장된 v-for 지시자로 메시지 리스트 렌더링
    v-for : <별칭> in <원본데이터>
    li 태그에 v-for 지시자를 추가해 별칭을 사용
  2. 이중 중괄호 구문을 이용해 리스트 내 객체의 text 프로퍼티와 createdAt 프로퍼티 출력
    이중 중괄호를 사용하면 출력결과와 해당 데이터 사이에 데이터 바인딩 생성
    : 태그를 실제 값으로 대체할 수 있으며 자바스크립트 표현식도 사용 가능 : {{message.text.toLowerCase()}}
  3. Vue의 지시자인 v-on을 이용해 submit 이벤트에 이벤트 리스너 부착
    prevent : 실제로 폼을 제출하지 않도록 event.preventDefault()호출 지시
    v-on을 이용해 DOM 이벤트에 리스너를 부착하고, Vue 사용자 정의 컴포넌트의 사용자 정의 이벤트 수신
  4. Vue의 지시자인 v-model을 이용해 textarea요소와 newMessage 프로퍼티 사이에 양방향 바인딩
    textarea 요소 값이 변경될 때마다 newMessage 자동으로 업데이트
    newMessage 요소 값이 변경될 때마다 textarea 자동으로 업데이트
  5. 폼의 submit 이벤트 트리거 할 수 있도록 버튼추가
  <div id="app">
    <ul>
      <li v-for="message in messages">
        {{ message.text }} - {{ message.createdAt }} 
      </li>
    </ul>    
    <form v-on:submit.prevent="addMessage">
      <textarea v-model="newMessage" placeholder="Leave a message">        
      </textarea>
      <div><button type="submit">Add</button></div>
    </form>
  </div>

 

(4) 양방향 바인딩

-이벤트를 수신하는 addMessage 메소드를 options 객체의 methods 프로퍼티로 메소드 생성

<script src="https://unpkg.com/vue@2.5.13/dist/vue.js"></script>
  <script>
    let vm = new Vue({
      el: '#app',
      data: {
        messages: [],
        newMessage: ''
      }, 
      computed: {        
        addDisabled () {
          return this.messages.length >= 10 || this.newMessage.length > 50
        }
      },
      methods: {
        addMessage (event) {
          if (!this.newMessage) return
          //배열에 새로운 메시지 추가 후, newMessage 프로퍼티 바인딩
          this.messages.push({text: this.newMessage, createdAt: new Date()})
          //newMessage 프로퍼티 초기화
          this.newMessage = ''
        },
        deleteMessage (message) {
          this.messages.splice(this.messages.indexOf(message), 1)
        }
      }
    })
  </script>

메소드 만들 때 화살표 함수는 사용 금지 -> this로 Vue 인스턴스에 접근 불가능하므로

 

(5) UI에서 메시지 삭제 방법 추가

 <li v-for="message in messages">
        {{ message.text }} - {{ message.createdAt }} 
        <button @click="deleteMessage(message)">X</button>
      </li>

 

(6) 버튼을 추가하고 이벤트 리스너에 메소드 연결

-메소드 이름 대신 인라인 구문 사용

<script>
    let vm = new Vue({
      el: '#app',
      data: {
        messages: [],
        newMessage: ''
      }, 
      computed: {        
        addDisabled () {
          return this.messages.length >= 10 || this.newMessage.length > 50
        }
      },
      methods: {
        addMessage (event) {
          if (!this.newMessage) return
          this.messages.push({text: this.newMessage, createdAt: new Date()})
          this.newMessage = ''
        },
        deleteMessage (message) {
          this.messages.splice(this.messages.indexOf(message), 1)
        }
      }
    })
  </script>

-Array.prototype.splice() 메소드가 messages 배열에서 선택된 메시지를 삭제한다.

-Vue.js를 이용하면 변경 사항을 감지해 DOM을 자동으로 업데이트한다.

 

(7) 메시지 추가하기 기능을 자동으로 비활성화하는 기능 추가

-리스트에 10개 메시지가 있으면 Add 버튼 비활성화

: v-bind 지시자를 이용 add 버튼의 disabled 속성과 message.length 표현식 연결해 배열의 길이 변경시 diabled 속성 자동 업데이트

<form @submit.prevent="addMessage">
      <textarea v-model="newMessage" placeholder="Leave a message">        
      </textarea>
      <div><button v-bind:disabled="messages.length >= 10" type="submit">Add</button></div>
     </form>

 

(8) 텍스트 길이가 50자 초과시 Add 버튼 비활성화

: v-bind 지시자를 이용 newMessage.length>50으로 변경

-> 이전 값에 추가해 messages.length >= 10 || newMessage.length>50로 변경하는데 유지 관리를 위해 computed 프로퍼티 이용

-> 종속된 대상을 추적하고 변경시 업데이트한다.

-> v-bind를 줄여 ':'로 표현 가능

      <div><button :disabled="addDisabled" type="submit">Add</button></div>
  <script>
    let vm = new Vue({
      el: '#app',
      data: {
        messages: [],
        newMessage: ''
      }, 
      computed: {        
        addDisabled () {
          return this.messages.length >= 10 || this.newMessage.length > 50
        }
      }
     })
  </script>

 

 

(9) v-bind 지시자로 HTML 요소의 내장된 속성 연결하고 템플릿 부착 지점에 v-cloak 지시자 추가

-Vue의 사용자 정의 컴포넌트의 프로퍼티 연결 가능

-v-cloak 지시자 추가 후, 템플릿 마크업을 숨기는 CSS 규칙 추가

-> DOM 준비 완료시 v-cloak 지시자 제거

  <style>
    [v-cloak] {display: none;}
    body > div {width: 500px; margin: 0 auto;}
    textarea {width: 100%;}
    ul {padding: 0 15px;}
  </style>
<div id="app" v-cloak>
    <ul>
      <li v-for="message in messages">
        {{ message.text }} - {{ message.createdAt }} 
        <button @click="deleteMessage(message)">X</button>
      </li>
    </ul>    
    <form @submit.prevent="addMessage">
      <textarea v-model="newMessage" placeholder="Leave a message">        
      </textarea>
      <div><button v-bind:disabled="addDisabled" type="submit">Add</button></div>
    </form>
  </div>

 

# Vue 인스턴스의 options 객체의 data 객체, computed 객체, methods 객체 활용 방법과 객체의 프로퍼티가 따로 분리 정의된 상태에서 this로 접근 가능

<!DOCTYPE html>
<html>
<head>
  <title>Messages App</title>
  <style>
    [v-cloak] {display: none;}
    body > div {width: 500px; margin: 0 auto;}
    textarea {width: 100%;}
    ul {padding: 0 15px;}
  </style>
</head>
<body>
  <div id="app" v-cloak>
    <ul>
      <li v-for="message in messages">
        {{ message.text }} - {{ message.createdAt }} 
        <button @click="deleteMessage(message)">X</button>
      </li>
    </ul>    
    <form @submit.prevent="addMessage">
      <textarea v-model="newMessage" placeholder="Leave a message">        
      </textarea>
      <div><button v-bind:disabled="addDisabled" type="submit">Add</button></div>
    </form>
  </div>
  <script src="https://unpkg.com/vue@2.5.13/dist/vue.js"></script>
  <script>
    let vm = new Vue({
      el: '#app',
      data: {
        messages: [],
        newMessage: ''
      }, 
      computed: {        
        addDisabled () {
          return this.messages.length >= 10 || this.newMessage.length > 50
        }
      },
      methods: {
        addMessage (event) {
          if (!this.newMessage) return
          this.messages.push({text: this.newMessage, createdAt: new Date()})
          this.newMessage = ''
        },
        deleteMessage (message) {
          this.messages.splice(this.messages.indexOf(message), 1)
        }
      }
    })
  </script>
</body>
</html>

컴포넌트

HTML 요소를 확장하고 추가 로직을 제공하며 해당 코드를 재사용하는 방법

-Vue 컴포넌트는 생성 중에 Vue 인스턴스와 같은 options객체를 받는다.

 

컴포넌트전역 등록

Vue.component (id, [definition])

첫 번째 인자는 템플릿에서 사용할 태그이름으로 컴포넌트의 id

두 번째 인자는 컴포넌트의 정의로 options 객체이거나 options 객체를 반환하는 함수

 

메시지 리스트 렌더링 순서

  1. 컴포넌트를 MessageList로, 태그이름을 message-list로 정의
  2. 템플릿 확장
  3. Delete 버튼 클릭 반응 추가

1. MessageList 컴포넌트가 data 객체 messages 프로퍼티에 접근 설정

: MessageList 컴포넌트에 items 프로퍼티 추가하고 v-bind 지시자로 messages 데이터와 연결

 

-Vue.js는 컴포넌트 자체 스코프를 가지므로 자식 컴포넌트에서 부모 데이터 직접 참조 불가능

-Vue.js가 제공하는 options 객체의 props 프로퍼티를 이용해 컴포넌트에 전달할 수 있는 데이터 정의

-props는 배열 또는 객체를 값으로 가진다.

 

2. 메시지 삭제 설정

: 렌더링 로직과 삭제 로직을 MessageList 컴포넌트에 있지만, <form>은 컴포넌트에 추가되지 않는다.

-하나의 컴포넌트는 하나의 책임에 집중해야하므로 MessageList 컴포넌트 내에서 Delete 버튼 클릭에 대한 메시지로 부모 컴포넌트와 통신하는 방법 사용

-Vue.js에서는 사용자 정의 이벤트를 이용해 부모 컴포넌트와 통신

 

현재 인스턴스의 이벤트를 트리거하는 $emit() 메소드를 이용해 첫 번째 인자로 이벤트명 두 번째 인자로 추가 데이터를 전달해

: 부모 컴포넌트가 v-on지시자를 이용해 이벤트를 리스너에 부착해 Delete 버튼 클릭시 MessageList가 delete 이벤트를 트리거하고 삭제할 메시지 전달

 

(1) index.html 수정해 컴포넌트 활용

<!DOCTYPE html>
<html>
<head>
  <title>Messages App</title>    
  <style>
    [v-cloak] {display: none;}
    body > div {width: 500px; margin: 0 auto;}
    textarea {width: 100%;}
    ul {padding: 0 15px;}
  </style>
</head>
<body>
  <div id="app" v-cloak>  
    <message-list :items="messages" @delete="deleteMessage"></message-list>
    <form @submit.prevent="addMessage">
      <textarea v-model="newMessage" placeholder="Leave a message"></textarea>
      <div><button :disabled="addDisabled" type="submit">Add</button></div>
    </form>
  </div>
  <script src="https://unpkg.com/vue@2.5.13/dist/vue.js"></script>  
  <script type="module">
    import MessageList from './components/MessageList.js'
    let vm = new Vue({
      el: '#app',
      data: {
        messages: [],
        newMessage: ''
      },
      computed: {        
        addDisabled () {
          return this.messages.length >= 10 || this.newMessage.length > 50
        }
      },    
      components: {
        MessageList
      },
      methods: {
        addMessage (event) {
          if (!this.newMessage) return
          let now = new Date()
          this.messages.push({text: this.newMessage, createdAt: now, id: now.getTime()})
          this.newMessage = ''
        },
        deleteMessage (message) {
          this.messages.splice(this.messages.indexOf(message), 1)
        }
      }
    })
  </script>
</body>
</html>

# v-bind 줄임말 ':', v-on 줄임말 '@', 사용자 정의 컴포넌트와 네이티브 HTML 요소를 같은 방식으로 데이터 바인딩하고 이벤트 리스너 부착

 

(2) MessageList 컴포넌트 생성

: Vue.js에서 컴포넌트 먼저 등록해 부모 컴포넌트에서 사용하도록 배치하고 컴포넌트 필요한 것들을 별도의 파일로 저장해 재사용

 <script type="module">
    import MessageList from './components/MessageList.js'
    let vm = new Vue({
      el: '#app',
      data: {
        messages: [],
        newMessage: ''
      },
      computed: {        
        addDisabled () {
          return this.messages.length >= 10 || this.newMessage.length > 50
        }
      },    
      components: {
        MessageList
      }
      });
 </script>

 

(3) components/MessageList.js 작성

export default {
  name: 'MessageList',
  template: `<ul>
    <li v-for="item in items" :item="item">
    {{ item.text }} - {{ item.createdAt }} 
    <button @click="deleteMessage(item)">X</button></li></ul>`,
  props: {
    items: {
      type: Array,
      required: true
    }
  },
  methods: {
    deleteMessage (message) {
      this.$emit('delete', message)
    }
  }
}

 

(4) 루트 Vue 인스턴스에 MessageList 등록

-ES6 모듈로 작성한 MessageList을 사용하기 위해 모듈 타입 추가

-컴포넌트에 사용할 MessageList:MessageList 등록

-Vue.js는 프로퍼티 이름을 파스칼 표기법에서 케밥 표기법으로 변환하고 컴포넌트 이름을 ID로 활용하므로, 템플릿에서 컴포넌트를 <message-list>로 사용 가능

-컴포넌트 이름 규칙 : 부모 컴포넌트와 밀접하게 연결된 자식 컴포넌트는 부모 컴포넌트의 이름을 접두사로 포함

 

components/MessageListItem.js

export default {
  name: 'MessageListItem',
  template: `<li>{{ item.text }} - {{ item.createdAt }} 
    <button @click="deleteClicked">X</button></li>`,
  props: {
    item: {
      type: Object,
      required: true
    }
  },
  methods: {
    deleteClicked () {
      this.$emit('delete')
    }
  }
}

# item 프로퍼티로 부모 컴포넌트의 데이터를 받을 수 있고, Delete 버튼을 클릭해 delete 이벤트 트리거 가능

 

components/MessageList

import MessageListItem from './MessageListItem.js'

export default {
  name: 'MessageList',
    template: `<ul><message-list-item v-for="item in items" 
    :item="item" :key="item.id"@delete="deleteMessage(item)">
    </message-list-item></ul>`,
  props: {
    items: {
      type: Array,
      required: true
    }
  },
  components: {
    MessageListItem
  },
  methods: {
    deleteMessage (message) {
      this.$emit('delete', message)
    }
  }
}

 


Vue 인스턴스 라이프 사이클

: 라이프 사이클 동안 각 단계별 로직을 정의하는 기능 제공

  1. beforeCreate
  2. created
  3. beforeMount
  4. mounted
  5. beforeUpdate
  6. updated
  7. activated
  8. deactivated
  9. beforeDestroy
  10. destroyed
  11. errorCaptured

지시자

: Vue 애플리케이션에서 표현식의 값 변경시 DOM에 변경 사항 적용

  1. v-for 지시자
    • 원본 데이터를 기반으로 요소 또는 데이터 블록을 여러 번 레더링
  2. v-on 지시자
    • 리스너를 DOM 요소에 부착
  3. 사용자 정의 지시자
    • 지시자 정의 객체 생성 후, Vue.directive()를 이용해 전역으로 등록
    • 지시자 정의 객체 생성 후, 컴포넌트의 directvie 프로퍼티를 이용해 해당 컴포넌트에 로컬로 등록

Vue.js가 지시자 외부에서 지시자에 전달하는 훅 함수를 지시자 정의 객체 내에 추가해 다양한 로직 적용

  1. bind
  2. inserted
  3. update
  4. componentUpdated
  5. unbind

 

v-focus 사용자 정의 지시자 예제

directives/focus.directive.js

// https://vuejs.org/v2/guide/custom-directive.html

// `v-focus`라는 전역 사용자 정의 지시자 등록하기
Vue.directive('focus', {
  //바인딩된 요소가 DOM으로 삽입될 때
  inserted: function (el) {
    // 요소에 초점을 둔다.
    el.focus()
  }
})

 

index.html

<!DOCTYPE html>
<html>
<head>
  <title>Messages App</title>    
  <style>
    [v-cloak] {display: none;}
    body > div {width: 500px; margin: 0 auto;}
    textarea {width: 100%;}
    ul {padding: 0 15px;}
  </style>
</head>
<body>
  <div id="app" v-cloak>  
    <message-list :items="messages" @delete="deleteMessage"></message-list>
    <form @submit.prevent="addMessage">
      <textarea v-model="newMessage" placeholder="Leave a message" v-focus></textarea>
      <div><button :disabled="addDisabled" type="submit">Add</button></div>
    </form>
  </div>
  <script src="https://unpkg.com/vue@2.5.13/dist/vue.js"></script>  
  <script type="module">
    import MessageList from './components/MessageList.js'
    import lifecyleLogger from './mixins/lifecycle-logger.mixin.js'
    import './directives/focus.directive.js'
    import './filters/datetime.filter.js'

    let vm = new Vue({
      el: '#app',
      name: 'MessagesApp',
      mixins: [lifecyleLogger],
      data: {
        messages: [],
        newMessage: ''
      },
      computed: {        
        addDisabled () {
          return this.messages.length >= 10 || this.newMessage.length > 50
        }
      },    
      components: {
        MessageList
      },
      methods: {
        addMessage (event) {
          if (!this.newMessage) return
          let now = new Date()
          this.messages.push({id: now.getTime(), text: this.newMessage, createdAt: now})
          this.newMessage = ''
        },
        deleteMessage (message) {
          this.messages.splice(this.messages.indexOf(message), 1)
        }
      }
    })
  </script>
</body>
</html>

 

<form @submit.prevent="addMessage">
      <textarea v-model="newMessage" placeholder="Leave a message" v-focus></textarea>
      <div><button :disabled="addDisabled" type="submit">Add</button></div>
    </form>

#지시자 파일을 ${directiveName}.directive.js형식으로 지정해 편집기에서 열 때 접미사로부터 지시자임을 알 수 있다.

 

 


필터

: Vue 애플리케이션에서 필터를 이용해 이중 중괄호 보간법 혹은 v-bind 표현법을 이용해 텍스트 형식을 지정

-필터는 표현식의 값을 첫 번째 인자를 가지는 자바스크립트 함수

-Vue.js는 필터를 등록하는 두 가지 방법

  1. Vue.filter()로 전역으로 등록
  2. 컴포넌트의 options 객체의 filters 프로퍼티를 로컬로 등록

createdAt 프로퍼티에 datetime 전역 필터 작성해 규약에 따라 파일명을 지시자 이름과 유사하게 명명

${filterName}.filter.js

 

filters/datetime.filter.js

//날짜와 시간 형식을 다루기 위해 Intl.DateTimeFormat 이용

const formatter = new Intl.DateTimeFormat('en-US', {
  year: 'numeric', month: 'long', week: 'long', day: 'numeric',
  hour: 'numeric', minute: 'numeric', second: 'numeric'
})
Vue.filter('datetime', function(value) {
  if (!value) return ''
  return formatter.format(value)
})

 

필터 적용

(1) index.html

<script type="module">
    import MessageList from './components/MessageList.js'
    import lifecyleLogger from './mixins/lifecycle-logger.mixin.js'
    import './directives/focus.directive.js'
    import './filters/datetime.filter.js'

    let vm = new Vue({
      el: '#app',
      name: 'MessagesApp',
      mixins: [lifecyleLogger],
      data: {
        messages: [],
        newMessage: ''
      },
      computed: {        
        addDisabled () {
          return this.messages.length >= 10 || this.newMessage.length > 50
        }
      },    
      components: {
        MessageList
      },
      methods: {
        addMessage (event) {
          if (!this.newMessage) return
          let now = new Date()
          this.messages.push({id: now.getTime(), text: this.newMessage, createdAt: now})
          this.newMessage = ''
        },
        deleteMessage (message) {
          this.messages.splice(this.messages.indexOf(message), 1)
        }
      }
    })
  </script>

 

(2) item.createdAt에 {{itemcreatedAt | datetime}} 필터 추가

import lifecyleLogger from '../mixins/lifecycle-logger.mixin.js'

export default {
  name: 'MessageListItem',
  mixins: [lifecyleLogger],
  template: `<li>{{ item.text }} - {{ item.createdAt | datetime }} 
    <button @click="deleteClicked">X</button></li>`,
  props: {
    item: {
      type: Object,
      required: true
    }
  },
  methods: {
    deleteClicked () {
      this.$emit('delete')
    }
  }
}

 

(3) date-fns 라이브러리 활용해 필터에 추가 인자 제공해 원하는 패턴 전달

: filter함수에 두 번째 매개변수 추가

Vue.filter('datetime', function(value, pattern){
	...
});

 

필터 사용시 패턴 지정 방법

{{ item.cratedAt | datetime('MM/DD/YYYY') }}

 


믹스인

: 코드를 재사용할 수 있는 또 다른 방법으로 모든 컴포넌트의 옵션을 포함 가능한 자바스크립트 객체

-Vue 컴포넌트의 options 객체에 믹스인을 혼합해 여러 컴포넌트에서 활용 가능

-로컬이나 전역으로 사용가능한데, 전역으로 사용시 생성되는 모든 Vue 인스턴스에 영향을 미친다.

 

 

mixins/lifecycle-logger.mixin.js

: 훅을 정의해 Vue 인스턴스의 라이프 사이클을 리턴하는 믹스인으로 내부에서 {this.$options.name}을 통해 인스턴스 접근

-$options는 인스턴스 프로퍼티로 Vue 컴포넌트 정의에 사용하는 options객체 참조해 this.$options.name으로 컴포넌트 이름에 접근

export default {  
  created () {
    console.log(`${this.$options.name} created`)
  },
  beforeMount () {
    console.log(`${this.$options.name} about to mount`)
  },
  mounted () {
    console.log(`${this.$options.name} mounted`)
  },  
  destroyed () {
    console.log(`${this.$options.name} destroyed`)
  }
}

 

 

믹스인 적용한 index.html

 <script type="module">
    import MessageList from './components/MessageList.js'
    import lifecyleLogger from './mixins/lifecycle-logger.mixin.js'
    import './directives/focus.directive.js'
    import './filters/datetime.filter.js'

    let vm = new Vue({
      el: '#app',
      name: 'MessagesApp',
      mixins: [lifecyleLogger],
      data: {
        messages: [],
        newMessage: ''
      },
      computed: {        
        addDisabled () {
          return this.messages.length >= 10 || this.newMessage.length > 50
        }
      },    
      components: {
        MessageList
      },
      methods: {
        addMessage (event) {
          if (!this.newMessage) return
          let now = new Date()
          this.messages.push({id: now.getTime(), text: this.newMessage, createdAt: now})
          this.newMessage = ''
        },
        deleteMessage (message) {
          this.messages.splice(this.messages.indexOf(message), 1)
        }
      }
    })
  </script>

 

믹스인 적용한 MessageList 컴포넌트

import lifecyleLogger from '../mixins/lifecycle-logger.mixin.js'
import MessageListItem from './MessageListItem.js'

export default {
  name: 'MessageList',
  mixins: [lifecyleLogger],
  template: `<ul><message-list-item v-for="item in items" 
    :item="item" :key="item.id"@delete="deleteMessage(item)">
    </message-list-item></ul>`,
  props: {
    items: {
      type: Array,
      required: true
    }
  },
  components: {
    MessageListItem
  },
  methods: {
    deleteMessage (message) {
      this.$emit('delete', message)
    }
  }
}

 

믹스인 적용한 MessageListItem

import lifecyleLogger from '../mixins/lifecycle-logger.mixin.js'

export default {
  name: 'MessageListItem',
  mixins: [lifecyleLogger],
  template: `<li>{{ item.text }} - {{ item.createdAt | datetime }} 
    <button @click="deleteClicked">X</button></li>`,
  props: {
    item: {
      type: Object,
      required: true
    }
  },
  methods: {
    deleteClicked () {
      this.$emit('delete')
    }
  }
}

#모든 인스턴스 라이플 사이클의 여러 단계를 확인해 AOP 적용


플러그인

: Vue.js 프레임워크에 확장성 제공

 

Vuex와 Vue Router, Vuelidate와 같은 플러그인 제공

 

 

plugins/lifecycle-logger.plugin.js

  1. install([vue 생성자], [options 객체]) 메서드를 가지는 일반 객체 생성
  2. install() 메소드 내부에서 vue 생성자에 추가
  3. 믹스인이 아닌 플러그인을 이용해 인스턴스의 라이프 사이클 추적 가능, 훅을 끌 수 있는 스위치 기능도 제공

이름 규칙을 이용해 명명

${pluginName}.plugin.js

 

const switchers = {
  created: true,
  beforeMount: true,
  mounted: true,
  destroyed: true
}

export default {
  install (Vue, options) {    
    Object.assign(switchers, options)
    
    Vue.mixin({  
      created () {
        if (switchers.created) {
          console.log(`${this.$options.name} created`)
        }        
      },
      beforeMount () {
        if (switchers.beforeMount) {
          console.log(`${this.$options.name} about to mount`)
        }        
      },
      mounted () {
        if (switchers.mounted) {
          console.log(`${this.$options.name} mounted`) 
        }        
      },  
      destroyed () {
        if (switchers.destroyed) {
          console.log(`${this.$options.name} destroyed`)
        }        
      }
    })
  }
}

 

Object.assign() 메소드로 options 객체 내의 switchers를 미리 정의된 switchers에 병합

-> 라이프 사이클 훅 함수를 전역 믹스인으로 적용

 

믹스인을 제거 후 플러그인 적용

<script type="module">
    import MessageList from './components/MessageList.js'    
    import LifecycleLogger from './plugins/lifecycle-logger.plugin.js'
    import './directives/focus.directive.js'
    import './filters/datetime.filter.js'

    //beforceMount 훅 추적 비활성화
    Vue.use(LifecycleLogger, {beforeMount: false})

    window.vm = new Vue({
      el: '#app',
      name: 'MessagesApp',
      data: {
        messages: [],
        newMessage: ''
      },
      computed: {        
        addDisabled () {
          return this.messages.length >= 10 || this.newMessage.length > 50
        }
      },
      components: {
        MessageList
      },
      methods: {
        addMessage (event) {
          if (!this.newMessage) return
          let now = new Date()
          this.messages.push({id: now.getTime(), text: this.newMessage, createdAt: now})
          this.newMessage = ''
        },
        deleteMessage (message) {
          this.messages.splice(this.messages.indexOf(message), 1)
        }
      }
    })
  </script>

반응형 시스템 동작 방식

: 반응형 데이터 반인딩 시스템을 이용해 데이터와 뷰를 동기화 상태로 유지한다.

 

-옵저버 디자인 패턴으로 데이터 의존성 수집 -> 데이터 변경시 와처에 통지

 

-Vue 인스턴스 초기화되는 동안 data 객체의 프로퍼티에 접근해 값을 업데이트하기 위해

Object.defineProperty() 메소드로 게터와 세터 함수를 생성해 data 객체의 모든 프로퍼티를 반응형으로 생성

 

-render 함수가 DOM 업데이트 시 템플릿에서 사용된 프로퍼티의 게터 함수 호출 -> 모든 Vue 컴포넌트 인스턴스에 대해 렌더 와처를 생성해 의존관계에 있는 프로퍼티 수집 -> 변경 추적해 렌더 와처에 통지 -> render 함수 트리거해 렌더 와처 DOM을 업데이트

 

 

 

반응형