javascript 객체에서 반응형 상태를 생성하기 위해서는 reactive() 함수를 사용할 수 있습니다.
<template>
<div>
<button v-on:click="increment">Click {{ state.count }}</button>
<button v-on:click="increment">Deep Click {{ state.deep.count }}</button>
</div>
</template>
<script>
import { reactive } from 'vue'
export default {
setup() {
const state = reactive({
count: 0,
deep: {
count: 0,
},
})
const increment = () => {
state.count++
state.deep.count++
}
return {
state,
increment,
}
},
}
</script>
<style lang=""></style>
state를 reactive 함수를 통해 선언 후, Click과 Deep Click 버튼을 클릭하면 화면에 노출되는 state.count 값이 1씩 올라가는 것을 확인할 수 있습니다.
그렇다면 다른 예시도 살펴 보겠습니다.
<template>
<div>
<p>{{ message }}</p>
<button v-on:click="addMessage">add click</button>
<hr />
</div>
</template>
<script>
import { reactive } from 'vue'
export default {
setup() {
let message = reactive('message')
const addMessage = () => {
message = message + '!'
}
return {
message,
addMessage,
}
},
}
</script>
<style lang=""></style>
위 예시에서는 add click 버튼을 클릭해도 화면에 노출되는 message에 !가 추가되지 않습니다. 왜 그럴까요?
Vue는 Javascipt를 기반으로 만들어졌기 때문에 Javascipt의 기본 특징을 따르기 때문입니다. 앞선 예시에서 state는 Object 타입으로 선언한 반면 message는 String 타입으로 선언했습니다. 이것은 Primitive와 Reference Type의 차이로, 잘 정리된 글이 있어 링크를 달아두도록 하겠습니다.
https://velog.io/@surim014/JavaScript-Primitive-Type-vs-Reference-Type
그렇다면 primitive type으로 reactive를 선언할 수는 없는 것일까요? 위 보기에서 반응성을 갖기 위해 message를 object로 감싸야 할까요?
reactive 함수가 reference type에만 동작하기 때문에, ref함수를 사용하여 primitive type으로 반응형을 만들 수 있습니다. ref 메서드는 metable한 객체를 반환합니다. 이 객체는 value라는 하나의 속성만을 포함합니다.
<template>
<div>
<p>{{ refMessage }}</p>
<button v-on:click="addRefMessage">add click</button>
<hr />
</div>
</template>
<script>
import { ref } from 'vue'
export default {
setup() {
let refMessage = ref('message')
const addRefMessage = () => {
refMessage.value += '!'
}
return {
refMessage,
addRefMessage,
}
},
}
</script>
<style lang=""></style>
위 예시에서는 add click 버튼을 클릭하면 화면에 !가 append 되어 노출되는 것을 확인할 수 있습니다. ref 함수에 대해 배웠듯, refMessage.value에 !를 추가하고 있습니다.
그런데 template 태그 내에서 {{ refMessage.value }}가 아닌, {{ refMessage }}로 작성했음에도 정상적으로 노출되는 이유는 무엇일까요?
<template>
<div></div>
</template>
<script>
import { reactive, ref } from 'vue'
export default {
setup() {
const count = ref(0)
const state = reactive({
count,
})
console.log(count.value) // 0
console.log(state.count) // 0
console.log(state.count.value) // undefined
return {}
},
}
</script>
<style lang=""></style>
ref 함수를 이용해 primitive type으로 반응형 상태를 만드는 것은 확인해 보았는데, 구조 분해 할당을 이용하면서 동시에 반응성을 잃지 않게하는 좋은 방법도 존재합니다.
const book = reactive({
author: 'Vue Team',
year: '2020',
price: 'free',
})
const { author, price } = book
위 코드에서 author과 price는 반응성을 잃지만,
const book = reactive({
author: 'Vue Team',
year: '2020',
price: 'free',
})
const { author, price } = toRefs(book)
toRefs를 사용하면 반응성이 유지됩니다.
const book = reactive({
author: 'Vue Team',
year: '2020',
price: 'free',
})
const author = toRef(book, 'author')
const price = toRef(book, 'price')
toRefs는 위처럼 사용할 수도 있습니다.
import { reactive, readonly } from 'vue'
const original = reactive({ count: 0 })
const copy = readonly(original)
original.count++ // 원본은 변이 가능, 의존하는 watch도 트리거 됩니다.
copy.count++ // 복사본은 readonly로 선언하였으므로, 경고와 함께 변경이 실패합니다.
반응형 데이터를 선언해두고, 변경 불가한 복사본을 만들어두고 싶을 수도 있습니다. 그럴 때는 readonly를 사용하면 됩니다.