티스토리 뷰
이번 포스트에서는 Vue.JS에서 비슷한 특징을 가진 computed
속성과 watch
속성의 특징을 이야기 하려고 합니다.
1. computed
속성
템플릿 문법([Vue.JS] 템플릿 문법 참고)에서 JavaScript 표현식을 사용하면, 쉽게 원하는 데이터를 DOM에 그릴 수 있습니다. 하지만 복잡한 연산을 템플릿 안에서 하게 되면, 코드를 이해하고, 유지보수하기 어려워집니다.
<div id="example">
{{ message.split('').reverse().join('') }}
</div>
위의 예제는 템플릿 문법 안에서 JavaScript를 사용하여 message
를 역순으로 출력한 예제입니다. 지금은 단순해 보일 수도 있지만 좀 더 복잡한 로직을 사용한다면 computed
속성을 사용하는 것이 좋습니다.
computed
사용 방법
<div id="example">
<p>원본 메시지: "{{ message }}"</p>
<p>역순으로 표시한 메시지: "{{ reversedMessage }}"</p>
</div>
new Vue({
el: '#example',
data: {
message: '안녕하세요'
},
computed: {
// 계산된 getter
reversedMessage: function () {
// `this` 는 vm 인스턴스를 가리킵니다.
return this.message.split('').reverse().join('')
}
}
})
위의 예제에서 computed
로 reversedMessage
를 선언했습니다. reversedMessage
를 getter 함수로 사용할 수 있게 됩니다.
computed
속성도 템플릿에 데이터 바인딩 할 수 있습니다. reversedMessage
는 message
에 의존적이기 때문에, message
의 값이 바뀌면 reversedMessage
의 값도 따라 바뀌게 됩니다. 즉 message
가 바뀔 때 reversedMessage
도 변경 되며, reversedMessage
와 바인딩 된 DOM이 업데이트하게 됩니다.
reversedMessage
를 보면 return되는 값으로 무엇을 나타낼지 선언하는, 선언형 프로그래밍 방식입니다.(위키백과 선언형 프로그래밍 참고) 앞으로 이야기 할 watch
는 명령형 프로그래밍 방식으로 computed
의 선언형 프로그래밍과 차이를 보입니다.
CodePen에서 위 예제 결과를 확인 할 있습니다.
computed
의 캐싱 VS methods
템플릿 문법의 JavaScript 표현식에서 methods
를 사용하여 위 예제의 computed
를 사용 한 결과와 동일한 결과를 얻을 수 있습니다.
<div id="example">
<p>원본 메시지: "{{ message }}"</p>
<p>역순으로 표시한 메시지: "{{ reversedMessage() }}"</p>
</div>
new Vue({
el: '#example',
data: {
message: '안녕하세요'
},
methods: {
reversedMessage: function () {
return this.message.split('').reverse().join('')
}
}
})
computed
를 사용하는 방법과 methods
를 사용하는 방법의 최종 결과는 동일합니다. computed
와 methods
의 차이점은 computed
는 종속 대상(reversedMessage
의 종속 대상은 message
)을 캐싱한다는 것입니다. 그렇기 때문에 computed
는 종속 대상이 변경 될 때만 함수를 호출합니다. message
값이 변하지 않는 한 reversedMessage
를 여러번 호출하여도 다시 계산하지 않고 캐싱한 결과를 즉시 반환합니다.
시간이 많이 걸리는 계산을 할 때, computed
을 사용하면 더 좋은 호율의 어플리케이션을 만들 수 있습니다. 캐싱을 하지 않고, 호출 할 때마다 새롭게 계산을 해야 하는 경우에는 methods
를 사용해야 합니다.
<div id="example">
<p>원본 메시지: "{{ message }}"</p>
<p>역순으로 표시한 메시지: "{{ reversedMessageByComputed }}"</p>
<p>역순으로 표시한 메시지: "{{ reversedMessageByComputed }}"</p>
<p>역순으로 표시한 메시지: "{{ reversedMessageByMethods() }}"</p>
<p>역순으로 표시한 메시지: "{{ reversedMessageByMethods() }}"</p>
</div>
new Vue({
el: '#example',
data: {
message: '안녕하세요'
},
computed: {
reversedMessageByComputed() {
console.log('computed reversedMessage')
return this.message.split('').reverse().join('')
}
},
methods: {
reversedMessageByMethods: function () {
console.log('methods reversedMessage')
return this.message.split('').reverse().join('')
}
}
})
위 예제의 실행 결과를 보면 computed
는 캐싱하고 있기 때문에, computed
의 reversedMessageByComputed
는 한 번, methods
의 reversedMessageByMethods
는 두 번 호출 되는 것을 확인 할 수 있습니다.
CodePen에서 위 예제 결과를 확인 할 수 있습니다.
주의사항 - computed
의 캐싱
Date.now()
와 같이 아무 곳에도 의존하지 않는 computed
속성은 절대로 업데이트 되지 않습니다.
computed: {
now: function () {
return Date.now()
}
}
업데이트 되지 않습니다.
위의 예시와 같이 Date.now()
를 리턴하는 computed
의 경우 업데이트 되지 않기 때문에, 몇 번을 사용하던 동일한 시간을 나타냅니다. 매번 호출 할 때 마다 현재 시간을 나타내고 싶다면, computed
가 아닌 methods
를 이용해야 합니다.
computed
VS watch
Vue.JS는 데이터가 변경 되었을 때 호출되는 콜백 함수를 정의하는 watch
속성을 제공합니다. watch
는 감시할 데이터를 지정하고 그 데이터가 바뀌면 어떠한 함수를 실행하라는 방식의 명령형 프로그래밍(위키백과 명령형 프로그래밍 참고) 방식입니다. 보통은 명령형 프로그래밍인 watch
보다는 선언형 프로그래밍인 computed
를 사용하는 것이 더 좋습니다.
<div id="demo">{{ fullName }}</div>
new Vue({
el: '#demo',
data: {
firstName: 'Foo',
lastName: 'Bar',
fullName: 'Foo Bar'
},
watch: {
firstName: function (val) {
this.fullName = val + ' ' + this.lastName
},
lastName: function (val) {
this.fullName = this.firstName + ' ' + val
}
}
})
위 예제는 watch
를 사용한 예제입니다. CodePen에서 위 예제 결과를 확인 할 수 있습니다.
<div id="demo">{{ fullName }}</div>
new Vue({
el: '#demo',
data: {
firstName: 'Foo',
lastName: 'Bar'
},
computed: {
fullName: function () {
return this.firstName + ' ' + this.lastName
}
}
})
위 예제는 computed
를 사용한 예제입니다. watch
를 사용했을 때 보다 코드가 더 간결해진 것을 볼 수 있습니다. CodePen에서 위 예제 결과를 확인 할 수 있습니다.
computed
의 setter 함수
computed
는 기본적으로 getter 함수입니다. 필요한 경우 setter 함수를 정의하여 사용 할 수 있습니다.
<div id="demo">{{ fullName }}</div>
new Vue({
el: '#demo',
data: {
firstName: 'Foo',
lastName: 'Bar'
},
computed: {
fullName: {
// getter
get: function () {
return this.firstName + ' ' + this.lastName
},
// setter
set: function (newValue) {
var names = newValue.split(' ')
this.firstName = names[0]
this.lastName = names[names.length - 1]
}
}
})
위의 예제는 computed
의 setter 함수를 정의한 예제입니다. CodePen에서 예제 결과를 확인 할 수 있습니다.
2. watch
속성
대부분의 경우 computed
속성을 사용하는 것이 좋습니다. 하지만 데이터 변경의 응답으로 비동기식 계산이 필요한 경우나 시간이 많이 소요되는 계산을 해야 할 때 watch
를 사용하는 것이 좋습니다. (저는 개인적으로 데이터가 변경되어 API를 호출해야 할 때, watch
를 사용합니다.)
<div id="watch-example">
<p>
yes/no 질문을 물어보세요:
<input v-model="question">
</p>
<p>{{ answer }}</p>
</div>
<!-- 이미 Ajax 라이브러리의 풍부한 생태계와 범용 유틸리티 메소드 컬렉션이 있기 때문에, -->
<!-- Vue 코어는 다시 만들지 않아 작게 유지됩니다. -->
<!-- 이것은 이미 익숙한 것을 선택할 수 있는 자유를 줍니다. -->
<script src="https://unpkg.com/axios@0.12.0/dist/axios.min.js"></script>
<script src="https://unpkg.com/lodash@4.13.1/lodash.min.js"></script>
<script>
var watchExampleVM = new Vue({
el: '#watch-example',
data: {
question: '',
answer: '질문을 하기 전까지는 대답할 수 없습니다.'
},
watch: {
// 질문이 변경될 때 마다 이 기능이 실행됩니다.
question: function (newQuestion) {
this.answer = '입력을 기다리는 중...'
this.getAnswer()
}
},
methods: {
// _.debounce는 lodash가 제공하는 기능으로
// 특히 시간이 많이 소요되는 작업을 실행할 수 있는 빈도를 제한합니다.
// 이 경우, 우리는 yesno.wtf/api 에 액세스 하는 빈도를 제한하고,
// 사용자가 ajax요청을 하기 전에 타이핑을 완전히 마칠 때까지 기다리길 바랍니다.
// _.debounce 함수(또는 이와 유사한 _.throttle)에 대한
// 자세한 내용을 보려면 https://lodash.com/docs#debounce 를 방문하세요.
getAnswer: _.debounce(
function () {
if (this.question.indexOf('?') === -1) {
this.answer = '질문에는 일반적으로 물음표가 포함 됩니다. ;-)'
return
}
this.answer = '생각중...'
var vm = this
axios.get('https://yesno.wtf/api')
.then(function (response) {
vm.answer = _.capitalize(response.data.answer)
})
.catch(function (error) {
vm.answer = '에러! API 요청에 오류가 있습니다. ' + error
})
},
// 사용자가 입력을 기다리는 시간(밀리세컨드) 입니다.
500
)
}
})
</script>
위에 예제는 Vue.JS 공식문서에서 watch
의 적절한 사용 방법을 나타낸 코드입니다. CodePen에서 위의 예제 결과를 확인 할 수 있습니다.
watch
를 사용하면, API롤 호출하고 그 결과에 대한 응답을 받기 전까지 중간 상태를 설정할 수있습니다. computed
를 사용하면 API 호출 결과를 기다리는 동안의 중간 상태을 설정할 수 없습니다.
참고
'Vue.JS' 카테고리의 다른 글
[Vue.JS] 조건부 렌더링 (0) | 2018.11.20 |
---|---|
[Vue.JS] 클래스와 스타일 바인딩 (0) | 2018.11.13 |
[Vue.JS] 템플릿 문법 (0) | 2018.10.23 |
[Vue.JS] 라이프 사이클 (4) | 2018.10.21 |
[Vue.JS] Vue 인스턴스 (0) | 2018.10.11 |