Content Table

cm-toc v.0.4 By 코마

쉽게 다가가는 Vue.js 이야기 (파트 5)

목차로 알아보는 Vue.js 의 초단간 개요: Computed 와 Watchers 속성

17 Sep 2019 by 코마 (gbkim1988@gmail.com)

안녕하세요 코마입니다. 오늘은 지난 시간에 이어서 Vue.js 의 Computed 와 Watcher 속성에 대해서 정리해보도록 할께요 😊. 이번 과정을 통해서 템플릿 문법의 복잡성을 줄이는 방법과 데이터 변경 작업 시 computed 속성으로 처리하기엔 시간이 긴 작업을 비동기로 해결하는 watcher 속성에 대해 알아보겠습니다. 👏👏👏 만약 이전 시간의 내용이 기억나지 않는다면 본문 내 참조된 이전 시리즈들의 링크들을 참고해주세요. 자 그럼 시작해볼까요? 😺

도입부

벌써 파트 5 입니다. Vue 스토리의 중반부에 이르렀습니다. 앞으로 여러분이 Vue 의 전반적인 내용을 모두 이해할 날이 얼마 남지 않았네요. 이전 과정을 복습하시려는 분들을 위해 이전 과정의 링크를 정리하였습니다.



Vue.js 는 컴포넌트를 논리적으로 분할하여 개발할 수 있는 시스템을 제공해 줍니다. 따라서, 여러분들은 아래와 같이 프로젝트 구조를 잡을 수 있습니다.

Root Instance
└─ TodoList
   ├─ TodoItem
   │  ├─ DeleteTodoButton
   │  └─ EditTodoButton
   └─ TodoListFooter
      ├─ ClearTodosButton
      └─ TodoListStatistics

위의 컴포넌트들 중에 EditTodoButton 컴포넌트를 살펴볼까요? Component 의 코드는 아래와 같습니다.

Vue.component('edit-todos-button', {
  data: function () {
    return {
      message: "o l l e H"
    }
  },
  template: `<div id="example">
  {{ message.split('').reverse().join('') }}
  </div>`
})

template 을 살펴보면 inline javascript expression 의 규약에 맞추어 힘겹게 message 를 처리하는 로직을 짜낸 것으로 보입니다. 만약에 여기에 좀 더 복잡한 로직이 들어가야 한다면 개발자 분들의 멘탈은 붕괴되고 말 것입니다.



Computed 속성

여기서 우리의 Vue.js 는 해결 책을 제공해줍니다. 바로 Computed 와 Watchers 속성입니다. 개선된 코드를 살펴볼까요?

Vue.component('edit-todos-button', {
  data: function () {
    return {
      message: "o l l e H"
    }
  },
  template: `<div id="example">
  {{ reversedMessage }}
  </div>`,
  computed: {
    reversedMessage: function() {
      return this.message.split('').reverse().join('')    }
  }
})

로직이 분할되었습니다. template 의 소스코드도 간결해 졌습니다. 그런데 다음과 같은 생각을 해볼 수 있습니다.

method 속성을 이용해서 구현하는 것과 똑같지 않은가?

맞습니다. 뭔가 특별한 마법이 숨겨져 있는 것처럼 보이지만, 사실 method 를 이용해 reversedMessage 함수를 구현하는 것과 동일합니다. 그렇지만 여기에는 분명한 차이점이 있습니다.

method 는 무조건 함수를 호출하도록 되어 있지만, computed 속성은 caching 을 사용하여 종속된 데이터의 변경이 있을 때에만 구문을 재평가합니다.

다시말해 this.message 의 값이 변경될 때만 {{ reversedMessage }} 가 재 평가됨을 의미합니다. 반면 method 속성의 함수는 매번 재호출되어 효율성 면에서 떨어집니다. 만약 여러분이 Vue 로 구성된 프론트 앱을 리팩터링 한다면 method 에 정의된 내용을 computed 속성으로 변경함으로써 앱의 속도를 향상시킬 수 있습니다. 😍

또한, reversedMessage 속성을 여러번 참조하는 경우 computed 속성으로 정의한 경우 이미 계산된 값을 캐싱하고 있다가 재출력 하기 때문에 더욱 효율적입니다. 만약 여러분이 캐싱을 원하지 않는다면 computed 속성에 정의된 로직을 method 로 옮겨주면 됩니다.

그런데, 여기서 의문이 또 발생할 수 있습니다.

method 에 정의하였다면 어떠한 기준으로 재 호출 되나요?

Vue.js 는 Virtual DOM 을 구성합니다. 그리고 데이터가 변경될 때마다 re-render 와 patch 가 호출됩니다. 이 과정을 체크하려면 beforeUpdate 와 updated 훅(hook)을 정의해주면 관찰이 용이합니다.

그렇다면 우리는 re-render 가 발생한다고 가정하였을 때, method, computed 속성 중 어디에 로직을 구현해 넣을지를 결정하면 문제가 심플해집니다.

Vue.js 의 라이프사이클에 대해 복습이 필요하다면 쉽게 다가가는 Vue.js 이야기 (파트3) 를 복습해주세요.



Computed Setter 란

아래의 예제를 살펴볼까요? 여러분은 이미 fullName 이라는 computed 속성을 정의하였습니다. 그리고 이 값은 사용자의 입력에 따라 변경될 수 있다고 가정해볼까요? 이러한 구현은 마이페이지나 회원가입 페이지에서 사용할 수 있습니다.

가상의 예이지만 아래와 같이 가정해 볼까요?

사용자의 이름을 “코드 마키나” 와 같이 입력을 받고 이를 띄워쓰기를 기준으로 “코드”, “마키나”와 같이 분할합니다. 그리고 첫번째 “코드”를 이름으로 “마키나”를 성으로 간주하고 각 데이터 속성에 반영하도록 합니다.

보통은 성과 이름을 따로 입력 받습니다. 이것은 가상의 예이니 이러한 부분은 차치하고 아래의 예를 확인해 볼까요?

<div id="demo">{{ fullName }}</div>
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]
    }
  }
  }
})

computed 속성의 fullName 에 get 과 set 이라는 키워드가 정의되었습니다. 우리가 흔히 getter, setter 라고 부르는 것입니다. setter 를 유심히 살펴봐 주세요. 새로운 인자를 받도록 되어 있으며 가상의 예를 구현하고 있습니다.

위의 구현을 통해 vm.fullName = "Code Machina" 라는 코드를 구현할 경우 firstNamelastName 은 setter 에서 정의된 로직에 의해 각각 Code, Machina 라는 문자열로 업데이트 됩니다.

computed 속성에 setter 를 추가하여 구현할 경우 응용의 폭이 넓어지는 효과가 있습니다.



Watchers 속성

드디어 Watchers 속성입니다. 여러분이 파일 시스템 상에서 파일이 변경되거나 추가될 경우 동작하는 WatchDog를 떠올린다면 이미 절반을 이해한 것입니다. 이제, Vue.js 가 watchers 속성을 구현한 철학을 이해하면 간편하게 활용할 수 있습니다.

watchers 속성은 데이터 변경과 관련하여 비동기적인 동작을 수행하거나 매우 많은 계산이 필요한 경우 유용합니다.

비용이 높은 처리 작업을 수행할 때, Watcher 의 사용을 고려한다 라고 간단히 머리 속에 정의해봅시다. 그리고 비용이 높은 처리 작업을 떠올려 볼까요? 저 코마의 머리 속에는 아래와 같은 항목들이 떠오릅니다.

이 중에 axios 를 이용한 외부 API 호출 예를 살펴보겠습니다.

<div id="watch-example">
  <p>
    API 서버에게 데이터를 호출하는 예제: 
    <input v-model="question">
  </p>
  <p>{{ answer }}</p>
</div>
<script src="https://cdn.jsdelivr.net/npm/axios@0.12.0/dist/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/lodash@4.13.1/lodash.min.js"></script>
<script>
var watchExampleVM = new Vue({
  el: '#watch-example',
  data: {
    question: '',
    answer: '제출할 질문을 작성해주세요.'
  },
  watch: {
    question: function (newQuestion, oldQuestion) {
      this.answer = '타이핑 완료를 기다리는 중입니다....'
      this.debouncedGetAnswer()
    }
  },
  created: function () {
    // lodash 라이브러리 중 debounce 함수는 재 요청까지 기다리는 시간을 제공합니다.
    // 즉, 500ms 에 한번씩 요청을 전송할 수 있음을 의미합니다.
    // 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>

created 훅을 통해 this.deboundcedGetAnswer 라는 함수를 정의하게 되었습니다. 그리고 lodash 라이브러리가 제공하는 debounce 함수를 통해 this.getAnswer 메서드를 500ms 마다 한번씩 호출할 수 있습니다.

그리고 watch 속성을 통해서 question 데이터가 변경될 경우 정의해둔 this.debouncedGetAnswer를 호출하는 구조입니다.

위의 예를 통해서 여러분들도 API 서버에 데이터를 전송하는 경우 watch 속성을 사용해 보시길 권해드립니다.



Watcher 와 Computed 속성

이 문서의 마지막 단계에 도달하셨습니다. 👏👏👏 (스스로에게 칭찬의 의미로 박수를 쳐주세요!) watch 와 computed 의 매력에 푹빠져들었다면 아래의 질문을 스스로에게 던져볼까요?

watch 와 computed 속성을 어떻게 구분해서 써야할까?

watch 는 분명 매우 편리합니다. 그러나 computed 또한 매우 편리합니다. 저 코마는 두 예를 비교함으로써 구현 시 예상되는 문제점을 지적하여 여러분들이 실전 코딩에서 효과적으로 이론을 적용하도록 정리해보았습니다.



watch 를 사용한 구현의 예

아래의 예는 firstName 과 lastName 데이터에 watcher 를 걸어두고 각 데이터가 변화할 경우 fullName 이라는 데이터를 반영하는 구조입니다.

여러분의 날카로운 눈이라면 이 예제가 중복된 코드를 사용하고 있음을 파악할 수 있습니다. 즉, 로직이 변경된다면 각 watcher 를 수정해야함을 의미합니다. 아래의 예제의 경우 두 번의 수정이 필요합니다.

코딩에 있어 중복된 로직을 반복하는 것은 유지 보수나 코드 길이 측면에서 지양 해야한다고 생각합니다.

var vm = new Vue({
  // .... 
  watch: {
    firstName: function(val) {
      this.fullName = val + ' ' + this.lastName
    },
    lastName: function(val) {
      this.fullName = this.firstName + ' ' + val
    }
  }
})

Computed 속성을 상용한 예

그렇다면 computed 속성을 사용한다면 어떻게 될까요? 동일한 구현이지만, 코드 길이가 줄었습니다. 그리고 더욱 심플해졌습니다. 앞으로 fullName 을 출력하는 로직이 변경할 경우 수정할 영역이 하나입니다.

var vm = new Vue({
  // .... 
 computed: {
    fullName: function () {
      return this.firstName + ' ' + this.lastName
    }
 }
})

정리

분명 watch 속성을 매우 편리합니다. 그러나 과용(overuse)는 지양되어야 합니다. 위의 비교를 통해서 여러분은 각 변수의 변화를 통해 다른 변수를 수정하는 로직 구현은 computed 속성이 더 나음을 알 수 있습니다. 이는 다루는 변수의 갯수가 증가 할수록 더욱 두드러지게 될 것입니다.



마무리

드디어 Vue.js 공식 문서 정복(뽀개기) 과정에서 Computed 와 Watcher 속성 영역이 완료되었습니다! 👏 (짝짝짝) 이 과정은 모든 문서를 정복할 때까지 매일 매일 업로드 하도록 할테니 내일 이 시간에도 시간을 내어 코마의 훈훈한 블로그 를 찾아주세요!

다음 시간에는 엘리먼트 어트리뷰트 중 classstyle 바인딩을 완벽하게 이해하실 수 있도록 정리해보도록 하겠습니다.

아직 드릴 이야기가 무궁무진하니 좀 더 지켜봐주시면 더욱 감사할 것 같아요! 여러분이 Vue.js 를 장난감처럼 가지고 노는 그날까지 저 코마는 멈추지 않겠습니다. 대한민국 IT인 여러분들의 건승을 기원합니다.

지금까지 코마 였습니다.

구독해주셔서 감사합니다. 더욱 좋은 내용으로 찾아뵙도록 하겠습니다. 감사합니다.



링크 정리

이번 시간에 참조한 링크는 아래와 같습니다. 잘 정리하셔서 필요할 때 사용하시길 바랍니다.

이 작가의 다음 글 감상