Watchers(감시자)

Watchers(감시자)

computed가 파생 값을 계산하는 데 적합하다면, watch는 애플리케이션 상태에 생긴 변화에 반응해서 프로세스를 실행하고 싶을 때 쓸 수 있어요. 예를 들어 카운터가 바뀔 때마다 비동기 작업을 하거나, 서버에 상태를 저장하거나, DOM을 조작하는 경우죠. watch 사용법과 감시 소스, 깊은 감시를 정리할게요.

출처: https://vuejs.org/guide/essentials/watchers.html

기본 예시

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

const question = ref('')
const answer = ref('Questions usually contain a question mark. ;-)')
const loading = ref(false)

// watch 는 ref, computed, reactive 객체, getter, 여러 소스 배열을 감시할 수 있어요.
watch(question, async (newQuestion, oldQuestion) => {
  if (newQuestion.indexOf('?') > -1) {
    loading.value = true
    answer.value = 'Thinking...'
    try {
      const res = await fetch('https://yesno.wtf/api')
      answer.value = (await res.json()).answer
    } catch (error) {
      answer.value = 'Error! Could not reach the API. ' + error
    } finally {
      loading.value = false
    }
  }
})
</script>

<template>
  <p>
    Ask a yes/no question:
    <input v-model="question" />
  </p>
  <p>{{ answer }}</p>
</template>

watch는 첫 인자로 감시 소스(예: question ref)를, 두 번째 콜백에는 새 값 newQuestion, 이전 값 oldQuestion을 받아요.

감시 소스 타입

watch의 첫 인자는 reactive 객체, ref, computed ref, getter 함수, 또는 위 소스들의 배열 등 다양한 타입이 될 수 있어요.

// getter
watch(
  () => x.value + y.value,
  (sum) => {
    console.log(`sum of x + y is: ${sum}`)
  }
)

// 여러 소스 배열
watch([x, () => y.value], ([newX, newY]) => {
  console.log(`x is ${newX} and y is ${newY}`)
})

주의할 점 — reactive 객체의 속성은 직접 감시할 수 없어요.

// ❌ 오류: getter 를 써야 해요
watch(obj.count, (count) => {
  console.log(`count is: ${count}`)
})

// ✅ 오히려 getter 로 감시
watch(
  () => obj.count,
  (count) => {
    console.log(`count is: ${count}`)
  }
)

getter를 쓰면 Vue가 의존성을 자동 추적해요.

깊은 감시(Deep Watchers)

reactive 객체에 getter를 직접 넘기면 암묵적으로 해당 객체의 속성까지 반응형 감시가 만들어져요. 하지만 객체의 속성 하나만 대는 getter(위 () => obj.count)는 그 속성 하나만 감시해요. 그런 객체 전체와 그 안의 중첩된 모든 값을 감시하고 싶다면 deep: true를 써요.

watch(
  () => state.someObject,
  callback,
  { deep: true }
)

커다란 데이터 구조를 깊은 감시하면 성능 비용이 커질 수 있으니, 필요한 경우에만 써야 해요.

즉시 실행($immediate)

immediate: true 옵션을 주면 콜백이 즉시 한 번 호출되고 나서, 소스가 바뀔 때마다 다시 호출돼요. 이러면 로딩 상태 등 초기화 로직을 하나로 합칠 수 있어요.

watch(
  source,
  (newValue, oldValue) => {
    // 즉시 실행된 뒤, source 가 바뀔 때마다 실행
  },
  { immediate: true }
)

watchEffect

watchEffect는 소스와 콜백을 분리하지 않고, 콜백이 의존하는 반응형 상태를 자동으로 추적해요. 콜백이 실행되는 동안 접근한 반응형 상태가 바뀌면 콜백이 다시 실행돼요.

watchEffect(async () => {
  const response = await fetch(url.value)
  data.value = await response.json()
})

URL이 바뀔 때마다 fetch가 다시 실행돼요. 콜백도 다시 실행되는 셈이죠.

watchwatchEffect의 핵심 차이는 의존성 추적 방식이에요. watch는 명시적으로 나열한 소스만 추적하고, watchEffect는 콜백 안에서 접근한 모든 반응형 상태를 추적해요. 특정 상태에 대한 명시적 반응(이전 값이 필요할 때)은 watch를, 콜백이 쓰는 여러 상태에 자동으로 반응해야 할 땐 watchEffect가 더 편리해요.

더 알아보기