vue.js/vue.js 공식문서

vue.js 공식문서 따라하기 - computed와 watch

반응형

* 공식문서 kr.vuejs.org/v2/guide/computed.html 를 공부하며 작성한 글입니다.

 

computed 속성(Properties)

템플릿 내에 표현식을 넣으면 편리합니다. 하지만 간단한 연산일 때만 이용하는 것이 좋습니다. 너무 많은 연산을 템플릿 안에서 하면 코드가 비대해지고 유지보수가 어렵습니다.

<div id="example">
  {{ message.split('').reverse().join('') }}
</div>

이 템플릿은 더 이상 간단하고 명료하지 않습니다. message를 역순으로 표시한다는 것을 알려면 찬찬히 살펴봐야 하겠죠. 템플릿에 메시지를 역순으로 표시할 일이 더 있으면 문제는 더 악화될 것입니다.

<div id="example">
  data1 : {{ message.split('').reverse().join('') }}
  data2 : {{ message.split('').reverse().join('') }}
</div>

복잡한 로직이라면 반드시 computed 속성 을 사용해야 하는 이유입니다.

 

Example

<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vue Sample</title>
  </head>
  <body>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <div id="example">
      <p>원본 메시지: "{{ message }}"</p>
      <p>역순으로 표시한 메시지: "{{ reversedMessage }}"</p>
    </div>
    <script>
    var vm = new Vue({
      el: '#example',
      data: {
        message: '안녕하세요'
      },
      computed: {
        // 계산된 getter
        reversedMessage: function () {
          // `this` 는 vm 인스턴스를 가리킵니다.
          return this.message.split('').reverse().join('')
        }
      }
    })
    </script>
  </body>
</html>

결과

이 예제에서는 computed 속성인 reversedMessage를 선언했습니다. 우리가 작성한 함수는 vm.reversedMessage속성에 대한 getter 함수로 사용됩니다.

console.log(vm.reversedMessage) // => '요세하녕안'
vm.message = 'Goodbye'
console.log(vm.reversedMessage) // => 'eybdooG'

콘솔에서 이 예제를 직접 해볼 수 있습니다. vm.reversedMessage의 값은 항상 vm.message의 값에 의존합니다.

일반 속성처럼 computed 속성에도 템플릿에서 데이터 바인딩 할 수 있습니다. Vue는 vm.reversedMessage가 vm.message에 의존하는 것을 알고 있기 때문에 vm.message가 바뀔 때 vm.reversedMessage에 의존하는 바인딩을 모두 업데이트할 것입니다. 그리고 가장 중요한 것은 우리가 선언적으로(역자 주: 선언형 프로그래밍 방식에 따라서(아래 computed와 watch 비교에 추가 설명)) 의존 관계를 만들었다는 것입니다. computed 속성의 getter 함수는 사이드 이펙트가 없어 코드를 테스트하거나 이해하기 쉽습니다.

 

computed Caching vs Methods

<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vue Sample</title>
  </head>
  <body>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <div id="example">
      <p>원본 메시지: "{{ message }}"</p>
      <p>역순으로 표시한 메시지: "{{ reversedMessage }}"</p>
      <p>뒤집힌 메시지: "{{ reversedMessage2() }}"</p>
    </div>
    <script>
    var vm = new Vue({
      el: '#example',
      data: {
        message: '안녕하세요'
      },
      computed: {
        // 계산된 getter
        reversedMessage: function () {
          // `this` 는 vm 인스턴스를 가리킵니다.
          return this.message.split('').reverse().join('')
        }
      },
      methods: {
        reversedMessage2: function () {
          return this.message.split('').reverse().join('')
        }
      }
    })
    </script>
  </body>
</html>

 

computed와 methods 는 같은 결과는 얻을수 있다. 차이점은 computed 속성은 종속 대상을 따라 저장(캐싱)된다는 것 입니다. computed에서 사용하고 있는 data속성인 message라는 프로퍼티가 변화가 있을때만 다시 연산을하고 한번 연산한 값을 캐싱 해놓았다가 필요한 부분에 다시 재 사용한다.

즉, message가 변경되지 않으면 computed 속성인 reversedMessage 요청해도 캐싱되어있는 결과를 가져온다. 또한 Date.now()처럼 아무 곳에도 의존하지 않는 computed 속성의 경우 절대 변경되지 않고 재사용 할수 있다.

반면 methods는 캐싱이라는 개념이 없기 때문에 매번 재 렌더링된다.

 


캐싱 효과 필요 -  computed

캐싱 효과 필요 x - methods

 

computed vs watch

<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vue Sample</title>
  </head>
  <body>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <div id="demo">
      <p>watch : {{ fullName }}</p>
      <p>computed : {{ fullName2 }}</p>
    </div>
    <script>
    var vm = 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
        }
      },
      computed: {
        fullName2: function () {
          return this.firstName + ' ' + this.lastName
        }
      }
    })
    </script>
  </body>
</html>

vue.js 는 데이터가 변경 되었을 때 호출되는 콜백 함수를 정의하는 watch 속성을 제공합니다. watch는 감시할 데이터를 지정하고 그 데이터가 바뀌면 어떠한 함수를 실행하라는 방식의 명령형 프로그래밍 방식입니다. 보통은 명령형 프로그래밍인 watch 보다는 선언형 프로그래밍인 computed를 사용하는 것이 더 좋습니다.

선언형 프로그래밍 - computed

명령형 프로그래밍 - watch

 

computed의 setter 함수

computed 속성은 기본적으로 getter 함수만 가지고 있지만, 필요한 경우 setter 함수를 만들어 쓸 수 있습니다.

<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vue Sample</title>
  </head>
  <body>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <div id="demo">
      <p>computed : {{ fullName }}</p>
    </div>
    <script>
    var vm = 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]
          }
        }
      }
    })
    </script>
  </body>
</html>

vm.fullName = 'John Doe'를 실행하면 setter 함수가 실행되면서 vm.firstName vm.lastName이 그에 따라 업데이트 됩니다.

watch 속성

대부분의 경우 computed 속성이 더 적합하지만 사용자가 만든 감시자가 필요한 경우가 있습니다. 그래서 Vue는 watch 옵션을 통해 데이터 변경에 반응하는 보다 일반적인 방법을 제공합니다. 이는 데이터 변경에 대한 응답으로 비동기식 또는 시간이 많이 소요되는 조작을 수행하려는 경우에 가장 유용합니다.

<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vue Sample</title>
  </head>
  <body>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <div id="watch-example">
      <p>
        yes/no 질문을 물어보세요:
        <input v-model="question">
      </p>
      <p>{{ answer }}</p>
    </div>
    <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.debouncedGetAnswer()
        }
      },
      created: function () {
        // _.debounce는 lodash가 제공하는 기능으로
        // 특히 시간이 많이 소요되는 작업을 실행할 수 있는 빈도를 제한합니다.
        // 이 경우, 우리는 yesno.wtf/api 에 액세스 하는 빈도를 제한하고,
        // 사용자가 ajax요청을 하기 전에 타이핑을 완전히 마칠 때까지 기다리길 바랍니다.
        // _.debounce 함수(또는 이와 유사한 _.throttle)에 대한
        // 자세한 내용을 보려면 https://lodash.com/docs#debounce 를 방문하세요.
        this.debouncedGetAnswer = _.debounce(this.getAnswer, 500)
      },
      methods: {
        getAnswer: 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
            })
        }
      }
    })
    </script>
  </body>
</html>

위에 예제는 Vue.JS 공식문서에서 watch의 적절한 사용 방법을 나타낸 코드입니다. watch를 사용하면, API롤 호출하고 그 결과에 대한 응답을 받기 전까지 중간 상태를 설정할 수있습니다. computed를 사용하면 API 호출 결과를 기다리는 동안의 중간 상태을 설정할 수 없습니다.

 

반응형