Vue3에서는 상태를 반응형으로 관리할 때 computed와 watch를 사용하는데 동작 방식과 목적이 다르다.
✨ computed
- computed는 어떤 상태(state)로부터 계산된 값을 만들 때 사용한다.
- ref와 달리 의존하는 값이 변경될 때만 다시 계산되기 때문에 성능 최적화에 유리하다.
- Composition API를 사용할 때 computed는 ref나 reactive를 기반으로 계산된 값을 반환하는 getter 함수를 받는다.
- computed는 기본적으로 읽기 전용(read-only) 이지만, getter와 setter를 함께 정의하면 값을 설정할 수도 있다.
언제 사용할까❓
- 반응형 상태를 기반으로 새로운 값을 만들 때
- 성능을 고려할 때 (computed는 결과를 캐싱한다.)
- 다른 데이터에서 의존적인 값을 만들 때
Example
<script setup>
import { ref, computed } from 'vue';
const price = ref(100);
const quantity = ref(2);
// 합계는 price와 quantity에 의존
const totalPrice = computed(() => price.value * quantity.value); // computed는 ref처럼 .value를 사용해서 접근한다.
</script>
<template>
<p>총 가격: {{ totalPrice }}</p>
</template>
📌 totalPrice는 price와 quantity가 바뀌었을 때만 다시 계산된다.
📌 computed는 값을 캐싱(cache) 하기 때문에, totalPrice를 여러 번 호출해도 동일한 값이면 다시 계산하지 않는다.
Example - getter와 setter 사용
computed는 기본적으로 읽기 전용(read-only) 이지만, getter와 setter를 함께 정의하면 값을 설정할 수도 있다.
<script setup>
import { ref, computed } from 'vue';
const count = ref(1);
const doubleCount = computed({
get: () => count.value * 2,
set: (newValue) => {
count.value = newValue / 2; // 새로운 값을 반으로 줄여 count에 저장
}
});
console.log(doubleCount.value); // 2
doubleCount.value = 10; // count = 5로 변경됨
console.log(count.value); // 5
console.log(doubleCount.value); // 10
</script>
📌 computed에 set을 정의하면 computed.value로 값을 설정할 수도 있다.
✨ watch
watch는 데이터가 변경될 때 특정 로직을 실행해야 할 때 사용한다.
언제 사용할까❓
- API 요청 같은 비동기 작업을 실행할 때
- 데이터 변화에 따라 다른 부수 효과(side effect)를 발생시킬 때
- 여러 개의 반응형 상태를 감시하고 특정 로직을 실행할 때
사용방법
<script setup>
import { ref, watch } from 'vue'
// 반응형 변수 선언
const 변수명 = ref(초기값)
// watch 사용법
watch(변수명, (새로운값, 이전값) => {
console.log(`변수가 변경됨: ${이전값} → ${새로운값}`)
})
</script>
Example
<script setup>
import { ref, watch } from 'vue';
const username = ref('');
// username이 변경될 때마다 API 호출
watch(username, async (newVal) => {
console.log(`새로운 사용자 이름: ${newVal}`);
// API 호출 같은 비동기 작업 수행 가능
});
</script>
<template>
<input v-model="username" placeholder="이름 입력" />
</template>
📌 watch는 값이 변경될 때마다 실행되기 때문에, API 요청 같은 비동기 작업에 유용하다.
📌 computed와 다르게, watch는 값을 반환하는 것이 아니라 어떤 동작(로직)을 수행하는 것이 목적이다.
✨ computed vs watch 비교
computed | watch | |
목적 | 계산된 값을 생성 (의존성 기반) | 값 변화 감지 후 특정 로직 실행 |
실행 시점 | 의존하는 값이 변경될 때 | 감시하는 값이 변경될 때 |
캐싱 여부 | O (같은 값이면 다시 계산 안 함) | X (매번 실행) |
비동기 처리 | X (비동기 지원 안 됨) | O (비동기 로직 가능) |
예제 | computed(() => price * quantity) | watch(username, async (newVal) => {...}) |
Setter 지원 | 가능 (옵션형 객체 사용) | 없음 (반응형 값 직접 수정 필요) |
🔍 watch 더 알아보기
여러개의 데이터를 감지하고 싶을 때
watch([firstName, lastName], callback)을 사용하면 배열로 여러 개의 데이터를 감지할 수 있다.
사용방법
<script setup>
import { ref, watch } from 'vue'
const 변수1 = ref('값1')
const 변수2 = ref('값2')
// 여러 개의 변수 감지
watch([변수1, 변수2], ([변수1의새로운값, 변수2의새로운값], [변수1의이전값, 변수2이전값]) => {
console.log(`변수1 변경: ${변수1의이전값} → ${변수1의새로운값}`)
console.log(`변수2 변경: ${변수2이전값} → ${변수2의새로운값}`)
})
</script>
Example
<script setup>
import { ref, watch } from 'vue';
const firstName = ref("Kim");
const lastName = ref("Lee");
watch([firstName, lastName], ([newFirst, newLast], [oldFirst, oldLast]) => {
console.log(`firstName: ${oldFirst} → ${newFirst}`);
console.log(`lastName: ${oldLast} → ${newLast}`);
});
</script>
<template>
<div>
<p>이름: {{ firstName }} {{ lastName }}</p>
<button @click="firstName = 'Park'">이름 변경</button>
<button @click="lastName = 'Choi'">성 변경</button>
</div>
</template>
deep - 객체 내부 속성 감지
- watch의 기본 상태는 지정한 속성의 값만 감시한다.
- 객체의 특정 속성을 감지할 때, 객체의 참조값만 감지하기 때문에 내부 속성 변화는 감지하지 못한다.
- 이럴 때 watch의 옵션인 deep: true 을 사용하면 지정한 속성 안에 있는 속성의 값도 감시한다.
<script setup>
import { ref, watch } from 'vue';
const person = ref({
name: "Kim",
age: 25
});
watch(person, (newValue, oldValue) => {
console.log(`변경된 값:`, newValue);
}, { deep: true });
</script>
<template>
<div>
<p>이름: {{ person.name }}, 나이: {{ person.age }}</p>
<button @click="person.age++">나이 증가</button>
</div>
</template>
immediate - 초기 실행
- watch는 기본적으로 값이 변경될 때 실행되지만, 처음 컴포넌트가 생성될 때 한 번 실행하고 싶다면 immediate 옵션을 사용하면 된다.
- immediate: true 를 설정하면 컴포넌트가 마운트될 때 한 번 실행되고, 이후 값이 변경될 때도 실행된다.
<script setup>
import { ref, watch } from 'vue';
const message = ref("안녕하세요");
watch(message, (newValue) => {
console.log(`message 값 변경됨: ${newValue}`);
}, { immediate: true });
</script>
<template>
<div>
<p>{{ message }}</p>
<button @click="message = '반갑습니다'">변경</button>
</div>
</template>
✨ watchEffect
- Vue 3의 반응형 데이터 변경을 감지하여 자동으로 실행되는 함수이다.
- watch()와 다르게 의존성을 명시적으로 지정하지 않아도 내부에서 사용하는 모든 반응형 변수를 자동으로 추적한다.
- React의 useEffect()와 비슷하게 컴포넌트가 처음 랜더링 될 때도 실행되지만 의존성 배열이 필요 없다는 점이 다르다.
<script setup>
import { ref, watchEffect } from "vue";
const count = ref(0);
watchEffect(() => {
console.log(`Count 값 변경됨: ${count.value}`);
});
function increment() {
count.value++;
}
</script>
<template>
<p>Count: {{ count }}</p>
<button @click="increment">+1</button>
</template>
📌 watchEffect() 내부에서 count.value를 사용했으므로 자동으로 추적된다.
📌 count.value가 변경될 때마다 콜백 함수가 실행된다.
📌 처음 실행될 때 한 번 실행되고, 이후 count.value가 바뀔 때마다 실행된다.
✨ watch vs watchEffect 비교
watchEffect는 모든 반응형 변수를 감지하기 때문에 불필요한 연산이 있을 수 있고 oldValue를 제공하지 않는다. watch와 다른점을 참고하여 상황에 맞게 적절한 것을 선택하는 것이 중요하다.
특징 | watch | watchEffect |
감지 대상 | 특정 반응형 변수 (명시적으로 지정) | 내부에서 사용된 모든 반응형 변수 (자동 추적) |
실행 시점 | 감지한 값이 변경될 때만 실행 | 선언 즉시 실행(컴포넌트가 처음 랜더링 될 때) + 값이 변경될 때 실행 |
deep 감지 | 기본적으로 deep: true 설정 가능 | 모든 반응형 객체 내부 속성도 자동 감지 |
oldValue 제공 | 제공됨 ((newValue, oldValue) => {}) | 제공되지 않음 |
최적화 | 감지할 대상을 제한할 수 있음 | 모든 반응형 데이터에 대한 종속성을 추적 (성능 부담 가능) |
'Vue.js' 카테고리의 다른 글
[Vue.js] 컴포넌트 렌더링 시 v-if & v-show & component 비교 (0) | 2025.02.12 |
---|---|
[Vue.js] v-for 사용시 key 값으로 index를 사용하지 않는 이유 (0) | 2025.02.07 |
[Vue.js] <script>가 <template>보다 위에 있어도 상관없을까? (1) | 2025.02.07 |