※ 일본의 한 블로그 글을 번역한 포스트입니다. 오역 및 의역, 직역이 있을 수 있으며 틀린 내용은 지적해주시면 감사하겠습니다.
computed란?
computed(산출 속성)는 리액티브한 데이터를 포함한 복잡한 로직을 기재하여 데이터의 변경이 있을 때, 자동적으로 재계산해주는 기능을 가지고 있다.
예를 들어, 템플릿 내에서 동적으로 변경된 email 속성을 기재하고 "@" 전의 문자를 소문자로 변환하는 처리를 하려고 한다.
<div>
<p>{{ email.split('@')[0].toLowerCase() }}</p>
</div>
Vue.js에 있어서는 이러한 리액티브한 데이터를 포함한 복잡한 로직을 처리하는 경우 기본적으로 computed를 사용한다. 반환값은 ref가 된다.
그러므로, ref도 동일하게 publishedBooksMessage.value로 산출된 결과를 참고하는 것이 가능하다.
<script setup>
import { reactive, computed } from 'vue'
const mail = reactive({
email: 'something@example.com'
})
// 산출 속성의 참고
const publishedBooksMessage = computed(() => {
return mail.email.split('@')[0].toLowerCase()
})
</script>
<template>
<p>{{ publishedBooksMessage }}</p>
</template>
watch이란?
Vue.js에서는 computed이외에도 watch가 존재한다. 어떤 쪽이든 등록한 속성의 변경을 검지하고 어떠한 처리를 하는 속성이다.
watch를 사용하여 감시하는 것이 가능해, 값이 변화하면 등록된 처리를 실행할 수 있다.
watch(감시대상, (다음의 상태, 전의 상태) => {
// 처리의 실행
} )
감시 대상으로는 프리미티브형이나 배열 등을 배치한다.
const x = ref(0)
watch(x, (newData) => {
console.log(${newData}`) // x가 변화할 때마다 리액티브한 값을 출력
})
리액티브 오브젝트의 감시
리액티브 오브젝트의 속성을 감시하는 경우, 그대로 속성을 전달하는 것만으로는 제대로 동작하지 않는다. 즉 아래의 지정 방식이라면 watch에 단순히 수치를 전달하기 때문이다.
const obj = reactive({ count: 0 })
watch(obj.count, (count) => {
console.log(`${count}`) // 제대로 동작하지 않는다.
})
오브젝트의 속성을 감시하는 경우는 getter를 사용하여 감시할 필요가 있다.
const obj = reactive({ count: 0 })
watch(
() => obj.count
(sum) => {
console.log(`${count}`)
}
)
여러 개의 감시하고 싶은 경우
배열로 정리하면 여러 개의 값을 감시하는 것이 가능하다.
const x = ref(0)
const y = ref(0)
// 여러 개의 요소의 배열
watch([x, y], ([newX, newY]) => {
console.log(`x is ${newX} and y is ${newY}`)
})
배열에 전달한 요소 중 하나만 변경된 경우는 watch가 발생하지만, 동시에 변경된 경우는 발생은 1번만이다.
// 동시에 값을 변경한다.
const onClick = () => {
++increment.value
++increment2.value
}
// 발생은 1번뿐
watch([increment, increment2], (next, prev) => {
// 처리
})
2번 처리를 발생시키기 위해서는 nextTick 메소드를 사용하여, 2번째는 DOM 갱신후에 watch가 동작하도록 한다.
const onClick = async () => {
++increment.value
await nextTick() // 추가
++increment2.value
}
watch([increment, increment2], (next, prev) => {
// 처리
})
deep
watch는 자동적으로 dee watcher가 적용된다. 그러므로, 괄호가 깊어진 오브젝트에 대해서도 watch는 문제없이 작동한다.
const obj = reactive(
{
count: 0 ,
age: {
year: 1980,
month: 10
day: 20
}
}
)
watch(obj, (count) => {
console.log(`${count}`) 괄호 안에 속성의 변경을 검지한다.
})
방금 언급했듯, getter을 사용한 경우는 오브젝트가 변경되지 않는 경우에 한해서는 watch가 동작하지 않지만, deep 오브젝트를 붙이는 것으로 강제적으로 괄호가 깊은 오브젝트에 대해서도 watch를 동작시킬 수 있다.
const obj = reactive(
{
count: 0 ,
age: {
year: 1980,
month: 10
day: 20
}
}
)
watch(() => obj.count, (count) => {
console.log(`${count}`) 괄호가 겹쳐있는 속성의 변경을 감지
},
{ deep: true }
)
compted와 메소드의 차이
위에서 봤던 코드를 아래와 같이 메소드로 쓸 수 있다.
// 메소드
function publishedBooksMessage() {
return mail.email.split('@')[0].toLowerCase()
}
그냥 보면 메소드도 문제 없이 동작할 것 같지만, computed로 기재한 경우와 명확한 차이가 있다.
캐시 기능이 있는지 없는지
computed로 산출된 값은 리액티브한 데이터로서 캐시되어 그 리액티브 데이터가 변경됐을 때에만 검지된다. 그러므로, 값이 변경되지 않는 경우의 한해서는 화면에 표시되는 값은 그대로가 된다.
computed의 경우, 리로드 됐어도, 캐시의 값을 참고하므로, 데이터가 변하지 않는한 표시의 변화가 없다. 한 편으로 메소드는 캐시 기능이 없으므로, 예를 들어, 데이터가 변경되지 않은 경우에도 페이지를 리로드할 때마다 새롭게 실행된다.
Getter/Setter을 사용할 수 있다.
computed에서는 getter과 setter을 기재하는 것이 가능하다. 부모 컴포넌트에서 데이터를 건내는 경우, 자식 컴포넌트는 props로 그 데이터를 받게 된다.
자식 컴포넌트에서 그 데이터를 변경하고 싶은 경우는 Setter를 사용하여 부모 컴포넌트에 대해서 이벤트를 보낼 필요가 있다.
<template>
<input type="text" :title v-model="initalValue" />
<button @click="clickButton">버튼</button>
</template>
<script>
import { toRef, computed } from 'vue';
export default {
emits: ['clickButton'],
props: {
value: {
type: String,
default: '',
},
},
setup(props, emit) {
// ref선언
const state = toRef(props, 'value');
const initalValue = computed({
get: () => {
return props.value;
},
set: (v) => {
if (props.value !== v) {
emit('clickButton', v);
}
},
});
return {
state,
};
},
};
</script>
computed와 watch의 차이
computed가 아닌, watch를 사용하는 경우 아래의 항목을 고려해야한다.
- 부작용이 변경될 것 같은 비동기 처리를 구현하는 경우
- 갱신 전과 갱신 후의 값을 취급하는 경우
- watch에 기재된 함수가 값을 반환할 필요가 없는 경우
<template>
<div id="app">
<p>
Ask a yes/no question:
<input v-model="question" />
</p>
<p>{{ demo }}</p>
</div>
</template>
<script>
import { ref, watch } from 'vue';
export default {
name: 'App',
setup() {
const question = ref('');
const demo = ref('');
watch(question, async (newQuestion, oldQuestion) => {
if (newQuestion) {
demo.value = 'something';
try {
const res = await fetch('https://yesno.wtf/api');
demo.value = (await res.json()).answer;
} catch (error) {
console.errer(error);
}
}
});
},
};
</script>
참고자료
'IT > 언어' 카테고리의 다른 글
[Vue.js] 부모 컴포넌트에서 자식 컴포넌트의 메소드 실행시키기 (0) | 2023.03.31 |
---|---|
[Vue.js/Vuetify] v-tooltip 사용법 (0) | 2023.03.29 |
[Vue.js] Vue를 사용한다면 알아두면 좋은 Vue 패턴과 잔기술 (0) | 2023.03.26 |
[JavaScript] some과 every (0) | 2023.03.21 |
[JavaScript] Object.entries()를 사용하여 Object를 배열로 변환하기 (0) | 2023.03.13 |