[Vue3] 12. Watcher

김관응·2023년 4월 12일
0

Vue

목록 보기
13/14

computed는 정해진 계산식에 따라 결과값을 반환하는 함수를 내가 호출하는 방식이다.
하지만 watcher는 호출을 하는게 아니라 지정한 값이 변경할 때 마다 인지하고 알아서 수행하는 방식이다.

Watcher 예제

<template>
  <p>
    예/아니오 질문:
    <input v-model="question" />
  </p>
  <p>{{ answer }}</p>
</template>

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

const question = ref('')
const answer = ref('질문에는 일반적으로 물음표가 포함됩니다.')

// watch는 ref에서 직접 작동합니다
watch(question, async (newQuestion, oldQuestion) => {
  if (newQuestion.indexOf('?') > -1) {
    answer.value = '생각 중...'
    try {
      const res = await fetch('https://yesno.wtf/api')
      answer.value = (await res.json()).answer === 'yes' ? '네' : '아니오'
    } catch (error) {
      answer.value = '에러! API에 연결할 수 없습니다. ' + error
    }
  }
})
</script>


input에 값을 입력하게 되면 watch에 의해 비동기로 함수가 실행 된다.

const x = ref(0)
const y = ref(0)

// 단일 ref
watch(x, (newX) => {
  console.log(`x값: ${newX}`)
})

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

// 여러 소스의 배열
watch([x, () => y.value], ([newX, newY]) => {
  console.log(`x는 ${newX}이고, y는 ${newY} 입니다.`)
})

watch 함수의 첫 번째 아규먼트는 감시 대상이다. ref, 객체, 배열이 될 수 있다.

const obj = reactive({ count: 0 })

// 이것은 watch()에 숫자를 전달하기 때문에 작동하지 않습니다.
watch(obj.count, (count) => {
  console.log(`count 값: ${count}`)
})

reactive는 감시 대상으로 지정할 수 없어 getter를 사용해야한다.

watch(
  () => obj.count,
  (count) => {
    console.log(`count 값: ${count}`)
  }
)

Deep Watchers

reactive 객체에서 watch를 호출하면 심층 watch가 생성된다.

이는 reactive를 getter로 사용하는것과 구별 해야한다.

deep watch를 사용하게 되면 reactive내 값이 변경되는거에 대하 콜백이 실행되지만, getter를 사용하는건 객체 자체가 변경되야 콜백이 호출이 된다..

const someObject = reactive({ count: 0, count2: 0 })

watch(someObject, (newValue, oldValue) => {
  // 중첩된 속성 변경사항이 있을 경우 실행됩니다.
  // 참고:
  // `newValue`와 `oldValue`는 같습니다.
  // 둘 다 동일한 객체를 참고하고 있기 때문입니다!
})

someObject.count++

예를들어 이렇게 deep watch를 이요하면 reactive 내에있는 count를 제외한 나머지 요소의 변화에 대해 모두 콜백이 실행되지만,

const state = reactive({
  someObject: { count: 0 }
})

watch(
  () => state.someObject,
  () => {
    // state.someObject가 교체될 때만 실행됩니다.
  }
)

이렇게 되면 state 안에 있는 someObject가 교체가 되야 콜백이 실행된다.

watch(
  () => state.someObject,
  (newValue, oldValue) => {
    // 참고: 
    // state.someObject가 교체되지 않는 한 여기에서
    // `newValue`와 `oldValue`는 같습니다.
  },
  { deep: true }
)

그러나 이렇게 deep 옵션을 주게 되면 reactice 내에 someObject가 교체되는것 이외에 someObject 내의 count가 변경되는 것 까지 확인해 콜백을 실행한다.

Eager Watchers

watch(source, (newValue, oldValue) => {
  // 즉시 실행된 다음 `source`가 변경되면 다시 실행됩니다.
}, { immediate: true })

immediate 옵션을 사용하면 변경과 관계없이 최초 한번 실행이 된다.

watchEffect()

watchEffect는 어떤 값을 감지할지 알려주지 않아도 안에 있는 값들을 알아서 확인 후 함수 수행을 해준다.

const url = ref('https://...')
const data = ref(null)

async function fetchData() {
  const response = await fetch(url.value)
  data.value = await response.json()
}

// 즉시 데이터 가져오기
fetchData()
// ...그런다음 url 변경을 감시하도록 watch를 실행합니다.
watch(url, fetchData)

하지만 위에 예제에 써놓은데로 초기에 데이터를 가져오고 값 변경을 감시하기 위해서는 아래와 같이 하면 된다.

const url = ref('https://...')
const data = ref(null)

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

watch vs watchEffect

watch : 명시적으로 감시된 소스만 추적. 소스가 실제로 변경된 경우에만 함수 실행. 콜백 실행 시기를 정확하게 제어 가능한 경우 사용.
watchEffect : 콜백 함수 내 모든 값 리스닝. 콜백 시기가 불규칙적인 경우 사용.

flush 옵션

watch에서 콜백 함수의 실행 타이밍을 조절할 수 있다.

  • 'pre'(기본값)
    렌더링 전에 콜백을 호출해야 함을 지정
    템플릿이 실행되기 전에 콜백이 다른 값을 업데이트 가능

  • 'post'
    렌더링이 끝날 때까지 콜백을 연기하는데 사용
    콜백이 $refs를 통해 업데이트 된 DOM 또는 하위 컴포넌트에 접근해야하는 경우에 사용

  • 'sync'
    콜백은 값이 변경되는 즉시 동기적으로 호출
watch(source, callback, {
  flush: 'post'
})

watchEffect(callback, {
  flush: 'sync'
})

watchPostEffect, watchSyncEffect또한 존재해 아래와같이 사용할 수 있다.

import { watchPostEffect, watchSyncEffect } from 'vue'

watchPostEffect(() => {
  /* Vue가 업데이트 된 후 실행됩니다 */
})

watchSyncEffect(() => {})

watch 중지하기

기본적으로 컴포넌트 안에서만들어진 watch는 컴포넌트가 마운트 해제되면 자동으로 중지된다.

하지만 watch가 비동기 안에서 생성이 되면 마운트가 해제되도 중지가 되지 않는다.

비동기 watch의 경우 반환할 함수를 만들어서 호출 해줘야 한다.

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

// 이 감시자는 컴포넌트가 마운트 해제되면 자동으로 중지됩니다.
watchEffect(() => {})

// ...하지만 이것은 자동으로 중지되지 않습니다.
setTimeout(() => {
  watchEffect(() => {})
}, 100)

const unwatch = watchEffect(() => {})

// ...나중에 감시자가 더 이상 필요하지 않을 때:
unwatch()
</script>

일반적으로는 watch를 비동기로 선언할 경우는 거의 없다.

일부 비동기 테이터를 기다려야 한다면 아래와같이 null로 초기화를 해놓고 기다리면 된다.

// 비동기적으로 로드할 데이터
const data = ref(null)

watchEffect(() => {
  if (data.value) {
    // 데이터가 로드될 때 실행될 로직
  }
})
profile
엔지니어였던 개발자

0개의 댓글

관련 채용 정보