
[Vue 3] Composition API - computed, methods, watch
Options API에서 computed, methods 그리고 watch에 대해서는 이미 다룬 적이 있습니다. 하지만 Composition API에서는 이 문법이 약간 달라지기에 어떤 식으로 바뀌는지 알아보도록 하겠습니다.
computed
Composition API 에서 computed는 Options API 처럼 함수를 넣어서 사용합니다. 계산된 값은 return으로 넣어주면 됩니다.
import { computed } from 'vue';
const computedVal = computed(function () {
return // ...
});
예를 들어 볼까요. 성과 이름을 합치는 예제입니다.
import { ref, computed } from 'vue';
const firstName = ref('John');
const lastName = ref('Doe');
const fullName = computed(function () {
return firstName.value + ' ' + lastName.value
});
getter setter
computed는 기본적으로 읽기 전용이지만 setter를 이용하면 쓰기도 가능하게 할 수 있습니다.
import { ref, computed } from 'vue';
const firstName = ref('John');
const lastName = ref('Doe');
const fullName = computed({
// getter
get() {
return firstName.value + ' ' + lastName.value
},
// setter
set(newValue) {
[firstName.value, lastName.value] = newValue.split(' ')
}
})
set에서는 새로운 값을 받아 이를 반응형 변수에 적절히 넣어주기만 하면 됩니다.
methods
사실 함수는 특별할 게 없습니다. setup 안에 함수를 선언하면 끝이기 때문입니다.
<script setup>
import { ref } from 'vue';
const count = ref(0);
function increase(event) {
count.value++;
}
</script>
<template>
<button @click="increase">{{ count }}</button>
</template>
버튼을 클릭하면 count가 증가하는 코드입니다.
watch
Options API에서는 감시(watch)할 변수를 함수명으로 넣었지만 watch는 매개변수로 넣어줘야합니다.
import { ref, watch } from 'vue'
const value = ref('')
watch(value, async (newVal, oldVal) => {
// value가 변할 때마다 특정 작업 처리
})
watch의 첫 번째 매개변수 source
위의 예제에서는 ref 값을 넣었지만 다른 것들도 들어갈 수 있습니다.
ref와 reactive같은 반응형 변수들은 이해가 가지만 getter function은 뭘까요? 말은 어렵지만 어떤 특정값을 내뱉는 함수라 생각하시면 됩니다.
const x = ref(0)
const y = ref(0)
watch(
// x.value나 y.value가 바뀌면 실행
function () {
return x.value + y.value;
},
// 위 함수 return 값이 바뀌면 실행
function (sum) {
console.log(`sum of x + y is: ${sum}`);
}
);
배열은 ref, reactive, computed, getter function 이 여러 개 들어간 것입니다. 한 종류만 들어갈 필요는 없습니다. 배열 값 중 어떤 것이라도 바뀐다면 watch 함수는 실행됩니다.
// ref와 getter function이 들어간 배열
watch([x, () => y.value], ([newX, newY]) => {
console.log(`x is ${newX} and y is ${newY}`)
})
reactive
를 사용할 때 약간 주의를 기울여야합니다. 객체를 선언했을 때 해당 속성 값을 넣어주면 watch 는 동작하지 않습니다.
const obj = reactive({ count: 0 })
// ❌ 숫자값이 들어가서 watch가 동작하지 않음
watch(obj.count, (count) => {
console.log(`count is: ${count}`)
})
// ✅ 대신 getter 함수를 사용하면 동작
watch(
() => obj.count,
(count) => {
console.log(`count is: ${count}`)
}
)
하지만 이렇게 굳이 속성값을 watch할 필요없습니다. 내부 속성까지 모두 감시하기 때문입니다.
Deep Watchers
기본적으로 reactive와 ref 값을 넣어주는 경우 중첩된(nested) 값들까지 모두 감시(watch)합니다.
const obj = reactive({ count: 0 })
watch(obj, (newValue, oldValue) => {
// obj.count++로 인해 동작합니다.
})
obj.count++
하지만 반응형이 아닌 하위 객체의 값을 넣는 경우는 깊게 감시(deep watch)하지 않습니다.
const obj = reactive({
childObj: { count: 0 }
);
watch(obj.childObj, (newValue, oldValue) => {
// ...
});
// watch 동작 안함
obj.childObj.count++;
// watch 동작
obj.childObj = { count: 1 };
그래서 이 경우 3번째에 객체를 전달해서 deep
옵션을 추가해줘야 동작합니다.
const obj = reactive({
childObj: { count: 0 }
);
watch(
obj.childObj,
(newValue, oldValue) => {
// ...
},
{ deep: true }
);
// watch 동작 안함
obj.childObj.count++;
// watch 동작
obj.childObj = { count: 1 };
watchEffect
watch는 처음 선언한 이후 값이 변해야만 콜백함수가 호출됩니다. 그런데 가끔은 선언되고 처음에 한 번 실행되길 원할 수도 있는데요. Options API에서는 immediate
라는 옵션을 줘서 최초에 한 번 실행되게 했었습니다.
export default {
data() {
return {
url: ""
}
},
watch: {
lang: {
immediate: true,
async handler() {
const fetchedData = await fetch(url);
}
}
}
}
watch
로 옵션을 넣는 것도 동일하게 가능합니다.
const obj = ref("Hello");
watch(
obj,
() => {},
{ immediate: true }
);
Composition API에서는 watch 대신 watchEffect
를 써주면 됩니다.
const url = ref("");
watchEffect(async ()=> {
const fetchedData = await fetch(url.value);
});
다만 다른 점은 첫 번째 매개변수에 감시할 대상을 넣지 않습니다. computed 처럼 내부에서 사용된 값을 찾아서 이 값이 변했는지 감시합니다.
watch vs watchEffect
둘의 차이는 첫 번째 매개변수에 source를 넣느냐의 차이입니다.
DOM 업데이트 된 이후 실행, flush
반응형 변수들이 값이 변하면 우선 watch가 실행된 이후 DOM이 업데이트 됩니다. 이 순서를 flush
옵션으로 바꿀 수 있습니다.
pre
(default): watch 콜백함수 실행 우선post
: DOM 업데이트 우선sync
: 값이 변할 때마다 watch 콜백함수 실행deep과 마찬가지로 콜백 함수 뒤에 옵션처럼 넣어주면 됩니다.
watch(source, callback, {
flush: 'post' // pre, sync
})
watchEffect(callback, {
flush: 'post' // pre, sync
})
// watchEffect + post
watchPostEffect(callback);
// watchEffect + sync
watchSyncEffect(callback);
watcher 멈추기
watch의 경우 setup에서 정의했다면 컴포넌트가 마운트 해제 되었을 때 자동으로 watch가 멈춥니다. 그런데 만약 비동기적으로 호출했다면 이 값은 자동으로 멈추지 않습니다.
import { watchEffect } from 'vue'
// 컴포넌트가 마운트 해제되었을 때 자동으로 멈춤
watchEffect(() => {})
setTimeout(() => {
// 자동으로 멈추지 않음
watchEffect(() => {})
}, 100)
하지만 이렇게 비동기적으로 watch를 만드는 경우는 거의 없습니다. 만약 만들었거나 수동으로 멈춰야하는 상황이라면, 반환된 함수를 호출하면 됩니다.
const unwatch = watchEffect(() => {})
// 감시 멈추기
unwatch()