[Vue.js] Watchers

2024. 7. 7. 20:47FE/Vue.js

 

 

Vue.js

Vue.js - The Progressive JavaScript Framework

vuejs.org

 


1. 왜 씀?

 

Computed Properties는 getter에서 발생할 수 있는 사이드 이펙트를 방지하기 위해서 getter는 순수함수여야 한다.

 

 

앞서 Computed Propertyis를 배우면서 위와 같이 배웠다.

 

상태 변경에 대한 반응으로 사이드 이펙트(ex. DOM을 변경하거나 비동기 작업의 결과를 기반으로 다른 상태를 변경하는 것)를 수행해야 하는 경우 어떻게 해야 할까?

 


2. watch function

 

Composition API를 사용한다면 wathch 함수를 사용해서 반응형 속성이 변경될 때마다 함수를 실행할 수 있다.

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

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

// watch works directly on a ref
watch(question, async (newQuestion, oldQuestion) => {
  if (newQuestion.includes('?')) {
    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" :disabled="loading" />
  </p>
  <p>{{ answer }}</p>
</template>

 


3. Watch Source Types

 

watch(source, (newValue, oldValue) => {...});

watch 함수의 첫 번째 인자인 source는 다양한 타입의 반응형인 무언가가 올 수 있다.

(ref (including computed refs), a reactive object, a getter function, or an array of multiple sources)

 

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

 

대신 반응형 객체의 속성을 얻어오는 getter function을 사용하면 된다:

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

 


4. Deep Watchers

 

reactive object를 source로 하는 watch()를 사용하면 기본적으로 deep watcher를 생성한다.

 

deep하게 모든 중첩된 요소들의 변화까지 감시한다.

 

const someObject = reactive({ count: 0 })

watch(someObject, (newValue, oldValue) => {
  // 중첩된 속성 변경사항이 있을 경우 실행됩니다.
})

someObject.count++

하지만 reactive object를 반환하는 getter 함수의 경우 주의해야 한다.

 

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

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

deep 옵션을 추가하면 위 예제도 deep watcher로 강제할 순 있다.

 

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

 


5. Eager Watchers

 

lazy하지 않고 열성적으로(?) 움직이는 watcher가 필요할 때가 있다.

 

여기서 lazy란 “감시된 소스가 변경되기 전까지 호출되지 않음”을 의미한다.

 

최초 데이터가 구성된 후 콜백이 실행되기를 원한단면 immediate: true 옵션을 전달해야 한다.

 

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

 


6. Once Watchers

 

3.4 버전 이상에서만 사용할 수 있다.

watch(
  source,
  (newValue, oldValue) => {
    // `source`가 변경될 때, 한 번만 트리거됨
  },
  { once: true }
)

소스가 변경될 때 콜백이 한 번만 트리거 되게 한다.

 


7. Callback Flush Timing

 

반응형 상태를 변경하면 컴포넌트 업데이트와 사용자가 만든 watcher callback이 모두 실행된다.

 

사용자가 만든 watcher callback은 중복 호출을 방지하기 위해 컴포넌트 업데이트와 유사하게, 동일한 "틱(tick)"에서 여러 번 상태 변경이 되더라도, 마지막에 한 번만 호출됩니다.

 

 

기본적으로, watcher의 callback은 부모 컴포넌트 업데이트 후, 자식 컴포넌트의 DOM 업데이트 이전에 호출된다.

 

이는 곧 watcher callback에서 자식 컴포넌트의 DOM에 접근하려고 할 때, DOM이 업데이트 전이란 것을 의미한다.

 


가. Post Watchers

 

 

Vue가 업데이트한 후에 watcher callback에서 자식 컴포넌트의 DOM에 접근하고자 한다면, flush: 'post' 옵션을 추가한다.

watch(source, callback, {
  flush: 'post'
})

 


나. Sync Watchers

 

 

동일한 틱 내에 여러 번 상태 변경 때마다 콜백을 호출해야 하는 경우, flush: 'sync' 옵션을 사용한다.

watch(source, callback, {
  flush: 'sync'
})

단, 일반적으로 엄청 비효율적이므로 정말 필요한지 다시 한번 고민해 보자.

 


'FE > Vue.js' 카테고리의 다른 글

[Vue.js] Props & Emit  (0) 2024.07.07
[Vue.js] Component  (0) 2024.07.07
[Vue.js] 이벤트 핸들링  (0) 2024.07.07
[Vue.js] Lifecycle Hooks  (0) 2024.07.07
[Vue.js] 리스트 렌더링  (0) 2024.07.07