기초 예제
computed 프로퍼티는 선언적으로 결과값을 계산하도록 한다. 하지만, 상태 변화에 따른 "side effects"를 실행할 필요가 있는 경우가 있다. 예를들면, DOM을 상태변화 시키거나, 비동기적인 작업 결과에 따른 상태 변경 등이다.
Composition API에서, 반응형 상태 변화 콜백의 트리거로 watch 함수를 사용할 수 있다.
<script setup>
import { ref, watch } from 'vue'
const question = ref('')
const answer = ref('Qestions usually contain a question mark. ;-_')
// watch는 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>
Watch의 소스 타입
watch의 첫 번째 아규먼트는 reactive "sources"의 다른 타입이 될 수 있다. 이건 ref( computed refs 포함), reactive object, getter function, 또는 여러개의 sources의 배열이다.
const x = ref(0)
cons ty = ref(0)
// ref 사용
watch(x, (newX) => {
console.log(`x is ${newX}`)
})
// getter
watch(
() => x.value + y.value,
(sum) => {
console.log(`sum of x + y is: ${sum}`)
}
)
// 여러 sources의 배열
watch([x, () => y.value], ([newX, newY]) => {
console.log(`x is ${newX} and y is ${newY}`)
})
이런 reactive object의 프로퍼티는 watch할 수 없다는걸 기억해라:
const obj = reactive({conunt: 0})
// watch()에 number를 넘기기 때문에 동작하지 않는다
watch(obj.count, (count) => {
console.log(`count is: ${count}`)
})
대신에 getter를 써라.
const obj = reactive({ count: 0 })
watch (
() => obj.count,
(count) => {
console.log(`count is: ${count}`)
}
)
깊은 watchers
reactive obejct에 직적접으로 watch()를 호출할 때, 내부적으로 deep watcher를 생성한다 - 콜백은 모든 중첩된 상태변화에 트리거된다.
const obj = reactive({ count: 0 })
watch(obj, (newValue, oldValue) => {
// 중첩 프로퍼티의 상태변화로 시작된다.
// 주의: 'newValue' 는 'oldValue'와 같다.
// 왜냐면 같은 obj를 가르키기 때문이다.
})
obj.count++
ractive object를 리턴하는 getter에 의해 변화될 수 있다. - 뒤에서, 콜백은 getter가 다른 오브첵트를 리턴했을 때만 시작될 것이다.
watch(
() => state.someObject,
() => {
// state.someObject가 변경되었을 때만 실행
}
}
하지만, 명시적으로 deep 옵션을 사용하면 두번째 케이스를 deep watcher로 강제할 수 있다.
watch(
() => state.someObject,
(newValue, oldValue) => {
// 주의: 'newValue'는 'oldValue'와 같다
// state.someObject가 변경되지 않는한..
},
{ deep: true }
)
주의 사항 Deep watch는 watched 객체의 모든 중첩 프로퍼티를 횡단하는걸 필요로 하며, 거대한 데이터 구조에서 사용될 때 매우 비싼 비용일 수 있다. 성능에 주의하며 필요한 경우에만 사용해야 한다. |
watchEffect()
watch()는 lazy다: 콜백은 watched source가 변경되지 않는한 호출되지 않는다. 하지만 eagerly하게 동작하는 같은 콜백 로직이 필요한 경우가 있다. 예를 들면, 어떤 초기화 데이터의 fetch를 원할 때, 그리고 연관된 상태의 변경이 일어났을 때 re-fetch를 원할 때이다. 아마 이렇게 할것이다:
const url = ref('https://...')
const data = ref(null)
async function fetchData() {
const response = await fetch(url.value)
data.value = await response.json()
}
// 즉시 fetch
fetchData()
// ... url 변경을 watch
watch(url, fetchData)
watchEffect()로 간소화 할 수 있다. watchEffect()는 자동적으로 effect의 반응형 의존성들을 추적해서 즉시 side effect를 수행하도록 한다. 위 예제는 이렇게 다시 작성할 수 있다:
const url = ref('https://...')
const data = ref(nuill)
watchEffect(async () => {
const response = await fetch(url.value)
data.value = await response.json()
})
이제, 콜백을 즉시 작동한다. 실행하는 동안, (computed properties와 비슷하게) 의존하는 url.value를 자동으로 추적한다. url.value가 변경될 때는 언제든지, 콜백이 다시 작동할 것이다.
액션에서 reactive data-fetching 과 watchEffect에 대한 예제는 여기서 확인할 수 있다.
TIP watchEffect 은 오직 동기화된 실행에서만 의존성들을 추적할 수 있다. 비동기적인 콜백에 사용할 때는, 첫번째 await tick 전에 프로퍼티에 접근해야한 추적된다. |
watch vs watchEffec
watch와 watchEffect는 둘다 반응형으로 사이드이펙트를 수행하도록 해준다. 주요 차이점은 반응형 의존성들을 추적하는 방법이다.
- watch는 오직 명시적으로 watched 된 소스만 추적한다. 콜백안에서 접근하는 어떤것도 추적하지 않는다. 게다가, 콜백은 오직 소스가 실제로 변경되었을 때만 발생한다. wath는 콜백이 시작되어야할 때 보다 정밀하게 컨트롤할 수 있도록, 사이드이펙트와 의존성 추적을 분리한다.
- watchEffect는 반면, 한 단계에 사이드 이펙트와 의존성 추적을 혼합한다. 이건 동기화된 실행의 모든 반응형 프로퍼티로의 접근을 자동으로 추적한다. 이건 좀더 편리하고 terser code에서 좀더 전형적인 결과를 만들지만, 반응형 의존성이 덜 명시적이게 만든다.
콜백 플러시 타이밍
반응형을 상태변화 시킬 때, 뷰 컴포넌트 업데이트와 watcher 콜백 둘 다 트리거 할 것이다.
기본적으로, user가 만든 watcher callbacks는 뷰 컴포넌트 업데이트 전에 호출된다. 이건 watcher callback. 안에 있는 돔에 접근을 시도하면, 돔은 뷰의 업데이트가 적용되기 전 상태라는 것이다.
만약 뷰가 업데이트를 적용한 후에 watcher callback 안에 있는 돔에 접근하기를 원한다면, flush: 'post' 옵션으로 명시해줘야만 한다.
watch(source, callback, {
flush: 'post'
})
watchEffect(callback, {
flush: 'post'
})
Post-flusth watchEffect()는 또한 편리한 별칭인, watchPostEffect()를 쓸 수 있다:
import { watchPostEffect } from 'vue'
watchPostEffect(() => {
/* Vue 업데이트 이후 실행됨 */
})
Watcher 멈추기
set()이나 <script setup>안에 동기적으로 선언된 Watchers들은 주인 컴포넌트 인스턴스에 바인드 되서, 주인 컴포넌트가 언마운트 되면 자동적으로 멈춘다. 대부분의 경우, watcher를 멈출 걱정을 안해도 된다.
열쇠는 watcher가 반드시 동기적으로 생성되야된다는 거싱다: 만약 watcher가 비동기적인 콜백으로 생성되면, 주인 컴포넌트에 바인드 되지 않고, 메모리 누수를 피하기 위해 수동으로 멈춰야 할 것이다. 그 예제이다:
<script setup>
import { watchEffect } from 'vue'
// 이건 자동으로 멈춘다
watchEffect(() => {})
// 이건 안멈춘다!
setTimeout(() => {
watchEffect(() => {})
}, 100)
</script>
watcher를 수동으로 멈추려면, returned handle function이라는 걸 써야 한다. 이건 watch와 watchEffect둘 다 동작한다:
const unwatch = watchEffect(() => {})
// ...나중에, 필요없어지면 닫힘
unwatch()
watcher를 비동기적으로 생성해야 하는 경우가 매우 적고, 동기저인 생성이 가능한 낫다는 걸 기억해라. 비동기적인 데이터를 기다려야 한다면, watch로직에 조건을 추가해서 만들 수 있다:
// 비동기적으로 데이터가 로드되는 상황임
const data = ref(null)
watchEffect(() => {
if (data.value) {
// data가 로드 됐을 때 뭔가 함
}
})
'front > vue' 카테고리의 다른 글
[vue3 공식문서 번역]Essentials.13.Components Basics (0) | 2022.04.28 |
---|---|
[vue3 공식문서 번역]Essentials.12.Template Refs (0) | 2022.04.27 |
[vue3 공식문서 번역]Essentials.10.Lifecycle Hooks (0) | 2022.04.25 |
[vue3 공식문서 번역]Essentials.9.Form Input Bindings (0) | 2022.04.25 |
[vue3 공식문서 번역]Essentials.8.Event Handling (0) | 2022.04.23 |
댓글