Vue 기본 문법 4. computed, method, watch 정리 (2편)

손근영·2025년 5월 14일

Vue

목록 보기
5/7

지난 글에서는 method로 계산된 값을 사용하는 경우, Vue가 화면을 렌더링할 때마다 함수가 반복 실행되어 불필요한 계산이 발생할 수 있다는 점을 확인했습니다.

이번 글에서는 이러한 상황을 어떻게 computed더 효율적으로 해결할 수 있는지 살펴보겠습니다.


computed 란?

computed는 Vue에서 계산된 값을 위한 반응형 속성입니다.
내부에서 사용하는 반응형 데이터가 바뀔 때만 다시 계산되고,
그 외에는 캐싱된 값을 재사용합니다.


예를 들어 아래 코드를 살펴봅시다.

<script setup>
import { ref, computed } from 'vue'

const count = ref(1)
const number = ref(1)

const double = computed(() => {
  console.log('computed 재실행')
  return count.value * 2
})
</script>

<template>
  <div>
    <p>count: {{ count }}</p>
    <p>double: {{ double }}</p>
    <p>number : {{ number }}</p>
    <button @click="count++">count 증가</button>
    <button @click="number++">number 증가</button>
  </div>
</template>

이 코드에서 countref(1)을 통해 생성된 반응형 변수입니다.
doublecount를 기반으로 계산되는 computed 속성이며,
count가 변경될 때에만 다시 계산되고, 그렇지 않으면 이전 결과를 재사용합니다.

즉, computed 내부에서 사용하는 의존성(반응형 변수) 이 바뀌면 해당 computed는 자동으로 재실행되지만, 그렇지 않으면 실행되지 않습니다.


우리의 예상은 이렇습니다

count를 증가시키면 → double이 다시 계산됨 (콘솔 로그 출력)

number를 증가시키면 → double과 관련 없기 때문에 재계산되지 않음


그렇다면 실제로 동작하는지 확인해볼까요?

실제로 count가 증가할 때만 computed가 동작하는 것을 볼 수 있습니다.


그럼 만약 computed 내부에서 의존하고있는 반응형 변수가 두 개 이상이면 어떻게 될까요 ?

예를 들어 다음과 같은 코드를 생각해볼 수 있습니다.

<script setup>
import { ref, computed } from 'vue'

const a = ref(1)
const b = ref(2)

const sum = computed(() => {
  console.log('sum 계산됨')
  return a.value + b.value
})
</script>

<template>
  <p>a: {{ a }} / b: {{ b }}</p>
  <p>합계: {{ sum }}</p>

  <button @click="a++">a 증가</button>
  <button @click="b++">b 증가</button>
</template>

버튼을 눌러보면 a 또는 b가 바뀔 때 sum이 다시 계산되는 것을 확인할 수 있습니다.


그렇다면 이런 의문이 들 수 있습니다

"a + b 정도의 간단한 연산이라면, 굳이 computed로 뺄 필요가 있을까?
템플릿 안에서 그냥 계산해도 되지 않나?"

정답은, 아닙니다.
단순히 "간단한 연산"이라고 해도,
템플릿 안에서 직접 계산하는 것은 권장되지 않습니다.


왜일까요?

템플릿에서의 표현식({{ a + b }})은 렌더링이 발생할 때마다 재실행됩니다.

Vue는 이 연산을 매번 다시 계산합니다.

연산이 많아질수록, 또는 이 표현식이 여러 번 반복되면 불필요한 오버헤드가 발생할 수 있습니다.

반면 computed의존하는 값이 바뀔 때만 계산합니다.

그 외에는 캐시된 결과를 재사용 합니다.


"계산은 JS에서 하고, 표시만 템플릿에서 하라"는 것이 Vue의 기본 철학입니다.

단순한 연산이라도 computed로 분리해두면
템플릿은 표현에만 집중하고,
로직은 JS에서 깔끔하게 관리할 수 있어
결과적으로 더 깔끔하고 안정적인 코드가 됩니다.


장바구니 예제에 computed 적용하기

지난 글에서 method로 작성했던 getTotalPrice()
이번에는 computed로 바꿔보겠습니다.

기존 방식

const getTotalPrice = () => {
  return cartItems.value.reduce(
    (sum, item) => sum + item.price * item.quantity,
    0
  )
}

<p>총합: {{ getTotalPrice() }}</p>

변경된 computed 방식

const totalPrice = computed(() => {
  console.log('computed 호출됨')
  return cartItems.value.reduce((sum, item) => sum + item.price * item.quantity, 0)
})
<p>{{ totalPrice }}</p>

cartItems가 변할 때만 computed가 호출되는 것을 볼 수 있습니다.


그렇다면 언제나 computed가 옳은 선택일까요?

단순히 생각하면,

“값이 바뀔 때만 자동으로 다시 계산되고,
그 외에는 캐시된 결과를 재사용한다면 당연히 computedmethod보다 더 효율적인 것 아닌가?”

라고 생각할 수 있습니다.

당연히 계산된 값을 화면에 표시하거나, 상태에 따라 UI를 구성하는 경우에는 computed가 매우 적합합니다.

하지만, 그렇다고 해서 모든 계산을 computed로 처리하는 것이 항상 정답은 아닙니다.

method호출될 때마다 실행되며, 반응형 데이터와 무관하게 동작합니다.

즉, 다음과 같은 경우에는 오히려 method를 사용하는 것이 더 자연스럽습니다.


1. 사용자 액션에 따라 매번 새로운 결과가 필요한 경우

const getCurrentTime = () => {
  return new Date().toLocaleTimeString()
}

<p>현재 시간: {{ getCurrentTime() }}</p>

시간은 매번 호출될 때마다 달라져야 하므로,
computed로 캐싱하면 오히려 원하지 않는 결과를 얻을 수 있습니다.

2. 동일한 함수를 여러 인자와 함께 재사용해야 하는 경우

const getPriceWithTax = (price) => {
  return price * 1.1
}

computed는 인자를 받지 못하므로, 이런 식의 계산은 method가 적절합니다.


결론은,

상태나 UI에 자동으로 반응해야 하는 계산된 값이라면 → computed

사용자 액션, 매번 달라지는 계산, 인자가 필요한 함수 → method

즉, 상황에 맞는 도구를 사용하는 것이 맞습니다.


그런데 이런 의문이 생길 수도 있습니다

특정 반응형 변수가 변경될 때,
그 값에 따라 다른 상태를 계산하거나,
또는 부수적인 동작(API 호출 등)을 실행해야 할 때는 어떻게 해야 할까요?

예를 들어, 학생의 점수에 따라 등급을 자동으로 계산하는 기능을 만든다고 가정해봅시다.
사용자는 슬라이더를 통해 점수(score)를 입력하고,
그에 따라 등급(grade)이 자동으로 결정되어 화면에 표시되어야 합니다.


1. computed 로 처리하기

<script setup>
import { ref, computed } from 'vue'

const score = ref(0)

const grade = computed(() => {
  if (score.value >= 90) return 'A'
  else if (score.value >= 80) return 'B'
  else if (score.value >= 70) return 'C'
  else if (score.value >= 60) return 'D'
  else return 'F'
})
</script>

<template>
  <input type="range" min="0" max="100" v-model="score" />
  <p>점수: {{ score }}</p>
  <p>등급: {{ grade }}</p>
</template>

이 코드처럼 computed를 사용하면
점수만 바뀌어도 등급이 자동으로 계산되고,
화면에 매끄럽게 반영됩니다.

그런데 만약,

계산된 grade 값을 단순히 화면에 보여주는 것에 그치지 않고,
서버에 전송하거나, 다른 상태에 저장하거나, 로그로 남겨야 한다면?


2. method로 처리하기

<script setup>
import { ref } from 'vue'

// 점수 상태
const score = ref(0)

// API 호출 시뮬레이션 함수
const sendToServer = (grade) => {
  console.log(`서버에 등급 전송됨: ${grade}`)
}

// 점수에 따른 등급 계산 함수
const calculateGrade = (score) => {
  if (score >= 90) return 'A'
  else if (score >= 80) return 'B'
  else if (score >= 70) return 'C'
  else if (score >= 60) return 'D'
  else return 'F'
}

// 화면에 표시할 등급 + API 호출까지 수행하는 method
const getGradeAndSend = () => {
  const grade = calculateGrade(score.value)
  sendToServer(grade) // 부수 효과: API 호출
  return grade
}
</script>

<template>
  <div>
    <h2>등급 계산 + 서버 전송 (method 방식)</h2>
    <input type="range" min="0" max="100" v-model="score" />
    <p>점수: {{ score }}</p>
    <p>등급: {{ getGradeAndSend() }}</p> <!-- 매 렌더링마다 실행됨 -->
  </div>
</template>

이 방식은 계산과 동시에 API 호출까지 수행합니다.
하지만 method의 동작 방식을 아는 여러분들은 이 코드에 큰 문제점이 있다는 것을 느끼실 겁니다.

그건 바로 getGradeAndSend()렌더링이 발생할 때마다 계속 호출된다는 점입니다.

실제로 엄청나게 호출되고 있음을 확인할 수 있습니다.

지금은 이 코드 한 줄이니까 괜찮아 보일 수 있습니다.

하지만 실제 프로젝트에서는 등급 외에도 예를 들어 자동 저장 기능, 상태 업데이트, 추천 항목 새로고침, 사용자 행동 로그 기록 ...

이런 부수 효과들이 여러 군데에서 동시에 발생하게 됩니다.
이때마다 렌더링이 일어날 때마다 메서드가 계속 실행된다면...?

성능은 엄청나게 저하될 것입니다.


이 문제를 해결하기 위해서..

그래서 등장하는 것이 바로 watch 입니다.

watch는 말 그대로 반응형 상태를 감시하다가,
값이 실제로 변경되었을 때 딱 한 번만 원하는 작업을 실행할 수 있게 도와줍니다.


다음 글에서는

watch의 기본 사용법과 computedmethod로는 대체할 수 없는 용도, 그리고 실제 코드에서 어떻게 유용하게 활용되는지를 실전 예제와 함께 자세히 살펴보겠습니다.

0개의 댓글