cover

Options API에서 computed, methods 그리고 watch에 대해서는 이미 다룬 적이 있습니다. 하지만 Composition API에서는 이 문법이 약간 달라지기에 어떤 식으로 바뀌는지 알아보도록 하겠습니다.


computed

Composition API 에서 computed는 Options API 처럼 함수를 넣어서 사용합니다. 계산된 값은 return으로 넣어주면 됩니다.

javascript
      import { computed } from 'vue';

const computedVal = computed(function () {
  return // ...
});
    

예를 들어 볼까요. 성과 이름을 합치는 예제입니다.

javascript
      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를 이용하면 쓰기도 가능하게 할 수 있습니다.

javascript
      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 안에 함수를 선언하면 끝이기 때문입니다.

html
      <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는 매개변수로 넣어줘야합니다.

javascript
      import { ref, watch } from 'vue'

const value = ref('')

watch(value, async (newVal, oldVal) => {
	// value가 변할 때마다 특정 작업 처리
})
    

watch의 첫 번째 매개변수 source

위의 예제에서는 ref 값을 넣었지만 다른 것들도 들어갈 수 있습니다.

ref
reactive
computed ref/reactive
getter function
배열

ref와 reactive같은 반응형 변수들은 이해가 가지만 getter function은 뭘까요? 말은 어렵지만 어떤 특정값을 내뱉는 함수라 생각하시면 됩니다.

javascript
      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 함수는 실행됩니다.

javascript
      // ref와 getter function이 들어간 배열
watch([x, () => y.value], ([newX, newY]) => {
  console.log(`x is ${newX} and y is ${newY}`)
})
    

reactive를 사용할 때 약간 주의를 기울여야합니다. 객체를 선언했을 때 해당 속성 값을 넣어주면 watch 는 동작하지 않습니다.

javascript
      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)합니다.

javascript
      const obj = reactive({ count: 0 })

watch(obj, (newValue, oldValue) => {
	// obj.count++로 인해 동작합니다.
})

obj.count++
    

하지만 반응형이 아닌 하위 객체의 값을 넣는 경우는 깊게 감시(deep watch)하지 않습니다.

javascript
      const obj = reactive({
	childObj: { count: 0 }
);

watch(obj.childObj, (newValue, oldValue) => {
	// ...
});

// watch 동작 안함
obj.childObj.count++;

// watch 동작
obj.childObj = { count: 1 };
    

그래서 이 경우 3번째에 객체를 전달해서 deep 옵션을 추가해줘야 동작합니다.

javascript
      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 라는 옵션을 줘서 최초에 한 번 실행되게 했었습니다.

javascript
      export default {
	data() {
		return {
			url: ""
		}
	},
	watch: {
		lang: {
			immediate: true,
			async handler() {
				const fetchedData = await fetch(url);
			}
		}
	}
}
    

watch로 옵션을 넣는 것도 동일하게 가능합니다.

javascript
      const obj = ref("Hello");
watch(
	obj, 
	() => {},
	{ immediate: true }
);
    

Composition API에서는 watch 대신 watchEffect를 써주면 됩니다.

javascript
      const url = ref("");
watchEffect(async ()=> {
	const fetchedData = await fetch(url.value);
});
    

다만 다른 점은 첫 번째 매개변수에 감시할 대상을 넣지 않습니다. computed 처럼 내부에서 사용된 값을 찾아서 이 값이 변했는지 감시합니다.

watch vs watchEffect

둘의 차이는 첫 번째 매개변수에 source를 넣느냐의 차이입니다.

watch는 감시해야할 대상을 넣기 때문에 해당 값이 변할 때만 콜백함수가 실행됩니다. 그래서 좀 더 콜백함수가 실행될 타이밍이 명시적입니다.
watchEffect는 자동으로 반응형 변수를 추적해서 감시하기 때문에 코드가 간결해지고 편하지만 어떤 변수에 의해 콜백함수가 실행될 지 명확하지 않습니다.

DOM 업데이트 된 이후 실행, flush

반응형 변수들이 값이 변하면 우선 watch가 실행된 이후 DOM이 업데이트 됩니다. 이 순서를 flush 옵션으로 바꿀 수 있습니다.

pre (default): watch 콜백함수 실행 우선
post: DOM 업데이트 우선
sync: 값이 변할 때마다 watch 콜백함수 실행

deep과 마찬가지로 콜백 함수 뒤에 옵션처럼 넣어주면 됩니다.

javascript
      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가 멈춥니다. 그런데 만약 비동기적으로 호출했다면 이 값은 자동으로 멈추지 않습니다.

javascript
      import { watchEffect } from 'vue'

// 컴포넌트가 마운트 해제되었을 때 자동으로 멈춤
watchEffect(() => {})

setTimeout(() => {
	// 자동으로 멈추지 않음
  watchEffect(() => {})
}, 100)
    

하지만 이렇게 비동기적으로 watch를 만드는 경우는 거의 없습니다. 만약 만들었거나 수동으로 멈춰야하는 상황이라면, 반환된 함수를 호출하면 됩니다.

javascript
      const unwatch = watchEffect(() => {})

// 감시 멈추기
unwatch()