티스토리 뷰

Vue.JS

[Vue.JS] computed와 watch

버미노트 2018. 10. 31. 00:45

이번 포스트에서는 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('')
    }
  }
})

위의 예제에서 computedreversedMessage를 선언했습니다. reversedMessage를 getter 함수로 사용할 수 있게 됩니다.

computed 속성도 템플릿에 데이터 바인딩 할 수 있습니다. reversedMessagemessage에 의존적이기 때문에, 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를 사용하는 방법의 최종 결과는 동일합니다. computedmethods의 차이점은 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는 캐싱하고 있기 때문에, computedreversedMessageByComputed는 한 번, methodsreversedMessageByMethods는 두 번 호출 되는 것을 확인 할 수 있습니다.

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
댓글
공지사항
최근에 올라온 글