개발 일기

[Vue.js 시작하기] - computed vs watch 본문

Vue.js

[Vue.js 시작하기] - computed vs watch

윤지 2021. 1. 14. 23:23

Vue에는 변화를 감지하는 프로퍼티인 computed와 watch가 있다. 비슷한 성격탓에 공식 문서에도 함께 다루는 것을 볼 수 있다. 충분히 헷갈릴 수 있지만 목적을 보면서 사용한다면 상황에 맞게 잘 쓸 수 있다.

 

kr.vuejs.org/v2/guide/computed.html

 

computed와 watch — Vue.js

Vue.js - 프로그레시브 자바스크립트 프레임워크

kr.vuejs.org

 

Computed

특정 data가 변화되었을 때, 복잡한 계산식을 계산하여 값을 return 해주는 속성이다.

 

<!--count.vue-->

<template>
  <div>
    {{ doubleCount }}
    <v-btn v-on:click="count++">클릭하면 두배</v-btn>
  </div>
</template>

<script>
export default {
  data() {
    return {
      count: 0,
    };
  },
  computed: {
    doubleCount: function () {
      return this.count * 2;
    },
  },
};
</script>

위의 예제에서는 버튼을 클릭하면 count가 +1이 되는데 이의 변화를 감지하는 computed에서 count의 두배를 계산해주고 있다. 상태 변화 값에 따른 계산식의 값을 return 해주고 있다는 것을 알 수 있다.

 

초기 화면에는 count 초기 정의대로 0인 것을 확인할 수 있고

두번 클릭했을 때는 2의 두배가 된 것을 확인할 수 있었다.

 

 

computed는 상위에서 하위 컴포넌트로 데이터를 전달할 수 있는 props 속성의 변화 또한 감시가 가능하다. 전달된 props의 형태를 바꾸어야 하는 경우의 예제를 살펴보자.

(props에 대해 모른다면 참조 : 2021/01/11 - [Vue.js] - Vue.js 시작하기 - 컴포넌트 데이터 전달 방법)

 

<!--parentComponent.vue-->

<template>
  <div>
    <v-btn @click="addCharacter">문자 추가</v-btn>
    <ChildComponent v-bind:parentCharacter="character" />
  </div>
</template>

<script>
import ChildComponent from "@/components/childComponent.vue";

export default {
  name: "ParentComponent",
  components: { ChildComponent },
  data: function () {
    return {
      character: "",
    };
  },
  methods: {
    addCharacter() {
      this.character += "A B C D";
    },
  },
};
</script>
<!--childComponent.vue-->

<template>
  <div>
    {{ normalizedCharacter }}
  </div>
</template>

<script>
export default {
  name: "ChildComponent",
  props: ["parentCharacter"],
  computed: {
    normalizedCharacter: function () {
      return this.parentCharacter.toLowerCase();
    },
  },
};
</script>

상위 컴포넌트에서 하위 컴포넌트로 대문자인 문자들을 props 속성을 사용하여 전달하고 있는 상황이다.

이때 원래 "" 였던 character data가 버튼을 누르면서 대문자들이 추가가 되어 상태가 변하게 되면 하위 컴포넌트에서  props 값을 소문자로 가공을 한다.

 

Watch

사용자가 만든 감시자가 필요한 경우, 쉽게 말해 특정 data에 대한 변화를 감지하고 그에 따른 함수가 실행해야할 때 사용한다. 

 

<!-- tabs.vue -->

<template>
  <v-card>
    <v-tabs v-model="tab" background-color="red lighten-2" dark>
      <v-tab v-for="n in length" :key="n"> Item {{ n }} </v-tab>
    </v-tabs>
    <v-card-text class="text-center">
      <v-btn :disabled="!length" text @click="length--"> Remove Tab </v-btn>
      <v-divider class="mx-4" vertical></v-divider>
      <v-btn text @click="length++"> Add Tab </v-btn>
    </v-card-text>
  </v-card>
</template>

<script>
export default {
  data: () => ({
    length: 15,
    tab: null,
  }),

  watch: {
    length(val) {
      this.tab = val - 1;
      console.log("tab : " + this.tab);
    },
  },
  created() {
    console.log("tab : " + this.tab);
  },
};
</script>

위는 Vuetify 문서에 있는 v-tab 사용 예제다.

 

v-tab 태그를 보게되면 v-for문을 length만큼 돌고있으니 탭에 length만큼의 구성요소가 있다.

(각 구성요소의 key는 자신의 길이에 해당한다.)

 

주목할 점은 Remove Tab 버튼과 Add Tab 버튼이다. 각각 length를 줄이고 늘리는 역할을 하고 있는데,

이때 watch 프로퍼티를 보게 되면 length data를 감시하고 있는 것을 확인할 수 있다. 

 

컴포넌트가 생성될때(created) tab data는 초기에 정의된대로 null이고, 그 이후 add tab 버튼을 누를 때 (length의 값이 변할 때) tab data가 length-1이 되는 것을 확인할 수 있다. 

=> length의 값이 변하게 되면 length - 1이 탭이 모델로 하고 있는 tab 데이터의 값이 된다.

 

 

watch 또한 상위에서 하위 컴포넌트로 데이터를 전달할 수 있는 props 속성의 변화 또한 감시가 가능하다.

 

이는 이어 나올 예제에서 살펴보자.

<!--parentComponent.vue-->

<template>
  <div>
    <v-btn @click="addList">list에 1부터 추가</v-btn>
    <ChildComponent v-bind:parentNumList="numList" />
  </div>
</template>

<script>
import ChildComponent from "@/components/childComponent.vue";

export default {
  name: "ParentComponent",
  components: { ChildComponent },
  data: function () {
    return {
      numList: [],
      num: 1,
    };
  },
  methods: {
    addList() {
      this.numList.push(this.numList);
      this.num++;
    },
  },
};
</script>
<!--childComponent.vue-->

<template>
  <div>
    {{ this.parentNumList }}
  </div>
</template>

<script>
export default {
  name: "ChildComponent",
  props: ["parentNumList"],
  watch: {
    parentNumList: {
      deep: true, // 속성 내부 검사.
      handler() {
        this.printParentNumList();
      },
    },
  },
  methods: {
    printParentNumList() {
      console.log(this.parentNumList);
    },
  },
};
</script>

다음은 상위 컴포넌트에서 버튼을 누를때마다 numList라는 배열에 1부터 순서대로 추가를 해주고

이를 하위 컴포넌트에 알려주기 위하여 props 속성으로 데이터를 넘겨주고 있는 예제다.

 
하위 컴포넌트에서는 props 값의 변화를 감지하기 위해서 watch에 props 값과 같은 이름의 감시자를 만들었다.

vue.js는 배열이나 객체를 watch할 때, 속성 내부가 변경되었다고 생각하지 않아서 deep : true를 사용하여 속성 내부를 검사를 요청해야 한다. 감시하고 있는 배열의 값이 변경되었다면 handler에서 해당 함수를 호출한다.

 

버튼을 7번 눌렀을때 다음과 같은 화면과 콘솔이 나온다.

 

결론

일반적으로 선언형 프로그래밍 (computed) 이 명령형 프로그래밍 (watch) 보다 코드 반복이 적은 등 우수하다고 평가하는 경향이 있으므로 computed를 사용할 수 있는 상황이라면 명령적인 watch 콜백보다 computed 속성을 사용하는 것이 더 좋다. watch를 남용하지 않고 목적에 맞게 사용하자.

 

computed : 계산된 값을 출력하는 용 

watch : 어떤 조건이 되었을 때 함수를 실행시키기 위한 트리거로서 사용

 

Comments