(준)공식 문서/Vue.js

[ Vue.js 3 공식 문서 ] 2. Essentials - Watchers

Je-chan 2022. 2. 22. 14:07

 

[ Reference ] 

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

 

Watchers | Vue.js

 

vuejs.org

 


1. Basic Example 

  Vue 에서는 ** computed ** 를 이용해서 값을 선언적으로 계산할 수 있다. 하지만 상태 변화에 따라 발생하는 사이드 이펙트를 다뤄야하는 상황이 발생하기도 한다. 예를 들어서 DOM 을 조작한다고 할 때, 혹은 비동기 연산의 결과에 따라 다른 상태를 변경해야하는 것 등이 있다. 이런 경우 Composition API 의 watch 함수를 사용하면 반응성 있는 상태가 변경될 때마다 콜백 함수를 실행할 수 있다. 

 

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

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

// watch works directly on a ref
watch(question, async (newQuestion, oldQuestion) => {
  if (newQuestion.indexOf('?') > -1) {
    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
    }
  }
})
</script>

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

 

  (* 위의 예제 코드를 잠깐 해석해보자면 question 이라고 하는 Ref 로 인해 반응성 있는 상태를 watch 함수를 이용해서 감시하고 있는 중이다. input 창으로 question 값이 바뀔 때 비동기 만약 '?' 가 question 내에 존재할 경우, 처음에 answer 값은 'Thinking...' 으로 바뀐다. 그리고 try - catch 문으로 넘어가서 비동기 작업을 진행한다. fetch 에 있는 경로로 api 요청을 하고 만약 이 요청이 정상적으로 수행된다면 answer 의 값은 응답 받은 파일의 answer 속성 값으로 변경하게 되고 네트워크 에러가 날 경우에는 catch 문에 있는 내용으로 answer 값이 바뀔 것이다. 이 예시는 하나의 상태 변화로 인해 다른 상태를 비동기 연산하여 변경하는 케이스가 된다. )

 

Watch Source Types

  watch 의 첫 번째 인자는 ** computed ** 를 포함해서 반응성 있는 상태, 반응성 있는 객체, getter 함수, 혹은 여러 다양한 요소를 갖고 있는 배열일 수 있다. 

 

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

// single ref
watch(x, (newX) => {
  console.log(`x is ${newX}`)
})

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

// array of multiple sources
watch([x, () => y.value], ([newX, newY]) => {
  console.log(`x is ${newX} and y is ${newY}`)
})

 

  반응성 있는 객체를 다룰 때는 다음과 같이 사용할 수 없다. 

 

const obj = reactive({ count: 0 })

// this won't work because we are passing a number to watch()
watch(obj.count, (count) => {
  console.log(`count is: ${count}`)
})

 

  대신에 getter 함수를 이용하면 원하는 결과를 가져올 수 있다. 

 

// instead, use a getter:
watch(
  () => obj.count,
  (count) => {
    console.log(`count is: ${count}`)
  }
)

 

  (* Vue 를 자주 사용해 본 사람이라면 저렇게 getter 함수를 이용해서 요소를 가져오는 경우가 많이 있다는 것을 알 수 있다. 예를 들어 props 의 default 로 지정해줄 때나, Vuex 를 사용할 때 state 의 값을 할당하는 등에서 저렇게 하는 경우가 종종 있었을 것이다.) 

 

  물론, watch 를 이용해서 반응성 있는 객체의 요소가 아닌 객체 그 자체를 가져올 수는 있다. 그 경우에는 Deep Watchers 가 발동한다. Deep 에서 알 수 있듯이 중첩된 객체에서도 변화가 감지되면 watch 가 실행된다. 

 

const obj = reactive({ count: 0 })

watch(obj, (newValue, oldValue) => {
  // fires on nested property mutations
  // Note: `newValue` will be equal to `oldValue` here
  // because they both point to the same object!
})

obj.count++

 

  바로 위의 예시와 바로 아래의 예시는 getter 함수를 사용했느냐 안 했느냐의 차이다. getter 함수의 경우 다른 객체를 반환하는 용도로 사용되는 것이며 만약에 이 반환된 새로운 객체에도 Deep watcher 를 적용하고 싶다면 옵션을 추가로 적용해야 한다.

 

watch(
  () => state.someObject,
  (newValue, oldValue) => {
    // Note: `newValue` will be equal to `oldValue` here
    // *unless* state.someObject has been replaced
  },
  { deep: true }
)

 

  Deep Watch 는 아무래도 중첩된 것까지 모두 감시하기 때문에 큰 데이터 구조에서 사용할 경우 비용이 많이 들 수 있다. 꼭 필요할 때만 사용하도록 해야 하고 성능에 미칠 수 있는 영향들은 주의해서 사용해야 한다.

 


2. watchEffect( )

  ** watch ** 는 감지하기로 한 인자가 변경되기 전까지는 콜백을 호출되지 않는다. 그러나 비동기로 초기 데이터를 가져오고 나서 그와 관련된 상태가 변경될 때마다 데이터를 다시 가져와야 하는 경우. 이런 경우에는 watchEffect( ) 를 이용해서 단순화할 수 있다. 이 hook 을 사용하면 반응 의존성을 알아서 자동으로 추적하고 발생할 수 있는 side effect 를 즉각적으로 수행해낼 수 있다. 

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

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

// fetch immediately
fetchData()
// ...then watch for url change
watch(url, fetchData)
watchEffect(async () => {
  const response = await fetch(url.value)
  data.value = await response.json()
})

 

  아래의 watchEffect 는 콜백 함수가 바로 실행된다. 그리고 실행될 때 자동으로 반응 의존성으로 url.value 를 추적하고 이 값이 변경될 때마다 콜백이 다시 실행된다. 

 

  ** watch ** 는 명시적으로 감시된 인자만을 추적한다. 콜백 내부에서 접근된 것 내용들은 추적할 수 없다. 또, 콜백 함수는 인자가 실제로 변경된 후에만 실행된다. watcher 의존성 추적과 side effect 를 분리해서 콜백이 실행돼야 할 시기를 보다 정확하게 제어할 수 있다.

 

  ** watchEffect ** 는 의존성 추적과 부작용을 한 번에 해결한다. 동기적으로 실행될 때 접근하는 모든 반응성 있는 상태들을 추적한다. 덕분에 더 편리하고 일반적으로 테서 코드가 생성되지만 반응 의존성이 덜 명확해진다는 단점이 있다