Watchers
Composition API를 사용하면 반응형 상태가 변경될 때마다 콜백을 트리거하는 watch
함수를 사용할 수 있다.
- 자세한 내용: watchers
<script setup>
import { ref, watch } from 'vue'
const question = ref('')
const answer = ref('질문에는 보통 물음표가 포함됩니다. ;-)')
const loading = ref(false)
// watch는 ref에 직접 작동합니다
watch(question, async (newQuestion, oldQuestion) => {
if (newQuestion.includes('?')) {
loading.value = true
answer.value = '생각 중...'
try {
const res = await fetch('https://yesno.wtf/api')
answer.value = (await res.json()).answer
} catch (error) {
answer.value = '오류! API에 도달할 수 없습니다. ' + error
} finally {
loading.value = false
}
}
})
</script>
<template>
<p>
예/아니오 질문을 하세요:
<input v-model="question" :disabled="loading" />
</p>
<p>{{ answer }}</p>
</template>
watch()
는 기본적으로 lazy.- 콜백은 감시된 소스가 변경되었을 때만 호출
- 첫 번째 인자: 감시될 소스
- 값을 반환하는 getter 함수
- ref
- 반응형 객체
- 또는 위에 나열한 것들의 배열
- 두 번째 인자: 소스가 변경될 때 호출될 콜백
- "변경된 값", "이전 값", "사이드 이펙트 클린업 콜백을 등록하기 위한 함수"
- 세 번째 인자(optional)
immediate
감시자가 생성되는 즉시 콜백이 호출.source
가 변경될 때 다시 실행- (최초 호출 시, 이전 값은
undefined
)
deep
소스가 객체인 경우, 깊은 변경사항에서도 콜백이 실행되도록 한다.- 참고: 깊은 감시자.
flush
콜백의 발생(flush) 타이밍을 조정flush
값이post
- 감시자 콜백에서 Vue가 DOM을 업데이트한 후 소유자 컴포넌트의 DOM에 접근
- 참고: 콜백 실행 타이밍,
watchEffect()
.
onTrack / onTrigger
감시자의 의존성을 디버그- 참고: 감시자 디버깅.
once
콜백을 한 번만 실행. 감시자는 첫 번째 콜백 실행 후 자동으로 중지!
watchEffect()
콜백의 반응형 종속성을 자동으로 추적할 수 있게 해줌
- React의
useEffect
와 유사함
watchEffect(async () => {
const response = await fetch(
`https://jsonplaceholder.typicode.com/todos/${todoId.value}`
)
data.value = await response.json()
})
3.5+ 버전
const { stop, pause, resume } = watchEffect(() => {})
// 임시로 감시자를 중지함
pause()
// 나중에 재개
resume()
// 중단
stop()
watch
와 watchEffect
비교
watch
와 watchEffect
는 모두 반응형으로 부수 효과를 수행할 수 있게 해줍니다. 주요 차이점은 반응형 종속성을 추적하는 방식입니다:
-
watch
는 명시적으로 감시된 소스만 추적- 콜백 내부에서 접근한 것은 추적하지 않는다.
- 또한, 소스가 실제로 변경되었을 때만 콜백이 실행된다.
watch
는 종속성 추적을 부수 효과와 분리하여 콜백이 실행될 시기를 더 정확하게 제어할 수 있다.
-
watchEffect
는 종속성 추적과 부수 효과를 하나의 단계로 결합한다.- 동기적 실행 동안 접근한 모든 반응형 속성을 자동으로 추적
- 이는 더 편리하며 일반적으로 더 간결한 코드를 작성할 수 있게 해주지만...
- 반응형 종속성이 덜 명확
부작용 정리
watcher에서 비동기 요청과 같은 부작용(side effects)을 수행한다.
watch(id, (newId) => {
fetch(`/api/${newId}`).then(() => {
// callback logic
})
})
- 요청이 완료되기 전에
id
가 변경된다면..?- 이전 요청이 완료되었을 때, 이미 오래된(stale)
id
값을 가진 상태에서 콜백이 실행 - 이상적으로는
id
가 새로운 값으로 변경될 때, 이전의 유효하지 않은 요청을 취소할 수 있어야 함
- 이전 요청이 완료되었을 때, 이미 오래된(stale)
- 이를 위해
onWatcherCleanup()
API를 사용- watcher가 무효화(invalidate)되고 다시 실행되기 전에 호출될 정리(cleanup) 함수를 등록할 수 있다.
import { watch, onWatcherCleanup } from 'vue'
watch(id, (newId) => {
const controller = new AbortController()
fetch(`/api/${newId}`, { signal: controller.signal }).then(() => {
// callback logic
})
onWatcherCleanup(() => {
// abort stale request
controller.abort()
})
})
콜백 플러시 타이밍
후처리 감시자
watch(source, callback, {
flush: 'post'
})
watchEffect(callback, {
flush: 'post'
})
import { watchPostEffect } from 'vue'
watchPostEffect(() => {
/* Vue 업데이트 후 실행됨 */
})
동기 감시자
Vue 관리 업데이트 이전에 동기적으로 실행되는 감시자
watch(source, callback, {
flush: 'sync'
})
watchEffect(callback, {
flush: 'sync'
})
import { watchSyncEffect } from 'vue'
watchSyncEffect(() => {
/* 반응형 데이터 변경 시 동기적으로 실행됨 */
})
감시자 중지
<script setup>
import { watchEffect } from 'vue'
// 이 감시자는 자동으로 중지됩니다.
watchEffect(() => {})
// ...이 감시자는 중지되지 않습니다!
setTimeout(() => {
watchEffect(() => {})
}, 100)
</script>
- 대부분의 경우 감시자를 직접 중지할 필요가 없지만...
- 감시자가 비동기 콜백 내에서 생성되면 소유자 컴포넌트에 바인딩되지 않으며
- 메모리 누수를 피하기 위해 수동으로 중지해야 한다.
const unwatch = watchEffect(() => {})
// ...나중에, 더 이상 필요하지 않을 때
unwatch()
- 감시자를 수동으로 중지하려면 반환된 핸들 함수를 사용
// 비동기적으로 로드될 데이터
const data = ref(null)
watchEffect(() => {
if (data.value) {
// 데이터가 로드될 때 무언가를 수행합니다.
}
})
- 비동기 데이터를 기다려야 하는 경우, 감시 로직을 조건부로 만들 수 있다.