Vue 3 프로젝트에서 현재 route 경로가 변할 때마다 다른 alert를 띄우는 기능을 개발 중이었는데, 웹사이트 내부 경로가 바뀔 때는 alert가 잘 떴지만 처음 진입 시에만 alert가 뜨지 않는 이슈를 발견했습니다.
// main page
<script setup lang="ts">
const route = useRoute();
// 페이지 진입시 동작하지 않음
watch([route], (oldValue, newValue) => {
window.alert(`현재 위치는 ${route.path}입니다.`);
})
</script>원인은 watch가 웹사이트 처음 진입 시점에는 동작하지 않는다는 점이었고, watch를 watchEffect로 변경한 뒤 alert가 정상적으로 출력되었습니다. watch와 watchEffect의 차이점을 정리합니다.
watch와 watchEffect란?
watch와 watchEffect는 Vue 2의 Options API에서 제공하던 watch와 유사한 컨셉을 가지고 있습니다. Vue 3에서 Composition API가 공식 도입되면서, 반응형 값(Reactive value)을 감지하기 위해 함께 제공됩니다.
※ Reactive value란? 참조 가능한 값(ref, reactive, computed 등)으로, 값을 변경하면 Vue가 변경 사항을 감지하고 해당 값을 참조하는 DOM과 반응형 값을 함께 업데이트합니다.

1. watch
// main page
<script setup lang="ts">
const route = useRoute();
// 페이지 진입시 동작하지 않음
watch([route], (oldValue, newValue) => {
window.alert(`현재 위치는 ${route.path}입니다.`);
})
</script>watch는 두 개의 필수 인자와 한 개의 선택 인자를 받습니다. 첫 번째 인자는 감시할 source, 두 번째 인자는 source가 변경될 때 호출될 callback 함수입니다.
※ source에 들어갈 수 있는 요소들
- 구독할 값을 리턴하는 getter 함수 (예시 : ()=> ref.value)
- ref, reactive와 같은 반응형 값
- 1번과 2번 등을 포함한 배열
여러 반응형 값들 중 특정 값만 명시적으로 추적하거나, 변화 이전 값(oldValue)까지 확인해야 한다면 watch를 사용하는 것이 적합합니다. watch는 lazy한 특성 때문에 명시적으로 입력한 값이 변경될 때만 동작하고, 기본적으로는 mount 이후의 변경만 추적합니다. 앞서 언급한 이슈도 페이지가 mount되기 전에 route 값이 먼저 확정되면서 발생했습니다. mount 여부와 관계없이 특정 값을 즉시 확인하고 싶다면 watchEffect를 사용하거나, 아래에 정리한 세 번째 인자(옵션)를 사용하시면 됩니다.
※ 세 번째 인자에 들어갈 수 있는 요소들
- immediate: watch가 생성되는 즉시 callback를 호출한다.
- deep: source가 object인 경우, deep한 변경 사항에서도 callback을 호출한다.
- flush: callback의 발생(flush) 타이밍을 조정할 때 사용되며 pre, post, sync 3가지 옵션이 존재한다. 각각 컴포넌트 업데이트 전, 업데이트 후, 동일한 틱 내에서 콜백 호출 여부를 지정한다.
- onTrack / onTrigger: watch를 디버깅하는 callback 함수를 지정한다.
// main page
<script setup lang="ts">
const route = useRoute();
// watch가 생성되는 즉시 동작
watch([route], (oldValue, newValue) => {
window.alert(`현재 위치는 ${route.path}입니다.`);
}, { immediate: true })
</script>watchEffect가 아니라 watch로 이번 이슈를 해결하려면, 세 번째 인자로 immediate: true를 넣어주시면 됩니다.
watch 특징 정리
앞서 말한 watch의 특징을 정리하면 다음과 같습니다.
- 필수 인자를 두 개, 선택 인자를 한 개 받는다.
- 처음에 컴포넌트가 mount될때 실행되지 않는다.
- 첫 번째 인자에 명시적으로 입력한 값들만 체크한다. 콜백 내에서 사용되는 다른 반응형 값들은 체크하지 않는다.
2. watchEffect
// main page
<script setup lang="ts">
const route = useRoute();
// 생성되는 즉시 동작
watchEffect(() => {
window.alert(`현재 위치는 ${route.path}입니다.`);
})
</script>watchEffect는 watch와 달리 한 개의 필수 인자와 한 개의 선택 인자를 받습니다. 첫 번째 인자로 callback 함수를 받으며, 선언되는 즉시 함수 내부에서 사용되는 반응형 값들을 자동으로 추적합니다. 이후 watchEffect 외부 요인으로 해당 값들이 변경될 때마다 callback을 다시 실행합니다. 다만 mount되지 않은 DOM에 접근하면 오류가 날 수 있으니 주의가 필요합니다.
두 번째 인자는 선택 값이며 watch와 마찬가지로 아래 옵션을 사용할 수 있습니다.
※ 두 번째 인자에 들어갈 수 있는 요소들
- flush: watch의 flush와 동일
- onTrack / onTrigger: watch의 onTrack, onTrigger와 동일
watchEffect 특징 정리
watchEffect의 특징을 정리하면 다음과 같습니다.
- 필수 인자와 선택 인자를 각각 한 개씩 받는다.
- 처음에 컴포넌트가 mount될때도 실행된다.
- 콜백 함수에서 사용되는 반응형 값들을 모두 추적한다. 따라서 watch보다는 덜 명시적이다.
3. 주의해야 할 엣지케이스
watch와 watchEffect를 사용할 때 놓치기 쉬운 실수들이 있습니다. 실제 프로젝트에서 겪을 수 있는 대표적인 케이스를 정리합니다.
cleanup 함수 누락
watchEffect는 콜백 인자로 onCleanup 함수를 받을 수 있습니다. 비동기 작업(API 호출, 타이머 등)을 수행할 때 cleanup을 누락하면, 값이 빠르게 변경될 경우 이전 요청의 응답이 나중에 도착해 의도하지 않은 데이터가 화면에 표시될 수 있습니다.
// cleanup 누락 - 이전 요청 결과가 나중에 도착하면 문제 발생
watchEffect(async () => {
const result = await fetchData(keyword.value);
data.value = result; // 이전 keyword의 결과가 덮어쓸 수 있음
});
// cleanup 적용 - 이전 요청을 무효화
watchEffect((onCleanup) => {
let canceled = false;
fetchData(keyword.value).then((result) => {
if (!canceled) data.value = result;
});
onCleanup(() => { canceled = true; });
});watch에서의 무한 루프
watch 콜백 내부에서 감시 대상 값을 직접 변경하면 무한 루프가 발생합니다. 의외로 복잡한 로직 안에서 간접적으로 값이 변경되는 경우에 놓치기 쉬운 실수입니다.
const count = ref(0);
// 무한 루프 발생 - count 변경 -> 콜백 실행 -> count 변경 -> ...
watch(count, (newVal) => {
count.value = newVal + 1; // 감시 대상을 콜백 내에서 변경
});
// 안전한 방식 - 조건부로 변경하여 루프 방지
watch(count, (newVal) => {
if (newVal < 10) {
count.value = newVal + 1;
}
});watchEffect도 마찬가지로, 콜백 내부에서 추적 중인 반응형 값을 변경하면 자기 자신을 다시 트리거하여 무한 루프에 빠질 수 있습니다. 이런 경우 Vue 런타임이 "Maximum recursive updates exceeded"라는 경고를 출력합니다.
4. 결론
사실 일반적인 경우에는 watch나 watchEffect 어느 것을 사용하셔도 큰 차이는 없습니다. 다만 둘의 차이와 사용 목적을 명확히 해두면, 상황에 맞는 선택을 하기에 도움이 된다고 생각합니다.
Watch를 사용하면 좋은 경우
- 명시적으로 추적할 값을 선언하여 필요한 상황에서만 값을 추적하길 원하는 경우
- 추적한 값의 old value를 보고 싶은 경우
WatchEffect를 사용하면 좋은 경우
- 명시적으로 특정 값만 추적하기 어려운 경우
- 컴포넌트가 mount될때 바로 사용하고 싶은 경우
참고 자료
Related Posts

이벤트 페이지 제작 공수를 줄이기 위한 드래그&드롭 빌더 개발기
이벤트 페이지를 만들 때마다 개발자가 직접 이미지를 S3에 올리고 HTML을 작성해야 했던 반복 작업을 없애기 위해, 드래그&드롭 기반 이벤트 페이지 빌더를 개발한 과정을 다룹니다.

웹뷰 환경에서의 구글 소셜 로그인 구현
웹뷰의 팝업 차단과 리다이렉트 제약을 극복하고, JavaScript-Native Bridge와 Android Intent Deep Link를 활용하여 안정적인 소셜 로그인 플로우를 구현한 과정을 다룹니다.

모바일 환경에서의 본인 인증 플로우 개선하기
window.open과 window.opener의 한계를 극복하고, sessionStorage를 활용하여 모바일 환경에서도 안정적으로 동작하는 본인 인증 플로우를 구현한 과정을 다룹니다.