반응형 상태 선언
ref()
컴포지션 API 에서 반응형 상태를 선언하는 권장 방법은 ref() 함수를 사용하는 것
import { ref } from 'vue'
const count = ref(0)
ref() 는 인수를 가져와서 .value 속성이 있는 ref 객체에 래핑하여 반환
const count = ref(0)
console.log(count) // { value: 0 }
console.log(count.value) // 0
count.value++
console.log(count.value) // 1
참고
https://ko.vuejs.org/guide/typescript/composition-api.html#typing-ref
싱글파일 컴포넌트 https://ko.vuejs.org/guide/scaling-up/sfc.html
<script setup>
setup을 따로 선언해서 하는것은 너무 장황하게 작성하는 방법 일 수 있기에
파일 컴포넌트(SFC) 를 사용해서 단순화 시킬 수 있다.
<script setup>
import { ref } from 'vue'
const count = ref(0)
function increment() {
count.value++
}
</script>
<template>
<button @click="increment">
{{ count }}
</button>
</template>
템플릿을 동일한 범위에서 선언된 js 함수로 생각하면 편하다
왜 Refs를 사용해야 하는가??
- 템플릿에서 ref 를 사용하고 나중에 ref 의 값을 변경하면, vue 는 자동으로 변경을 감지, DOM을 업데이트 함
- 컴포넌트가 처음 렌더링 될 때, Vue는 렌더링 과정에서 모든 ref를 추적하고, 변경되면 추적하는 컴포넌트를 재렌더링 트리거함
- js에는 일반 변수의 접근이나 변형을 감지하는 방법이 없지만, getter setter 메서드를 사용해서 객체의 속성의 get 및 set 연산을 가로챌 수 있음
- .value 속성은 ref 가 엑세스 되거나 변경되었을 때 감지할 기회를 줌, getter 에서 추적하고 setter 에서 트리거를 수행
ref 를 다음과 같은 객체라고 생각할 수 있음
// 실제 구현이 아닌 유사 코드
const myRef = {
_value: 0,
get value() {
track()
return this._value
},
set value(newValue) {
this._value = newValue
trigger()
}
}
깊은 반응형
refs 는 깊게 중첩된 개체, 배열 또는 Map 과 같은 js 내장 데이터 구조를 포함하여 모든 값 유형을 보유할 수 있음
중첩된 객체나 배열을 변경하더라도 변경 사항이 감지될 것으로 예상 가능
import { ref } from 'vue'
const obj = ref({
nested: { count: 0 },
arr: ['foo', 'bar']
})
function mutateDeeply() {
// 예상대로 작동합니다
obj.value.nested.count++
obj.value.arr.push('baz')
}
얕은 참조 - https://ko.vuejs.org/api/reactivity-advanced.html#shallowref
의 경우 반응성을 위해 .value 액세스만 추적, 큰 개체의 관찰 비용을 피하거나 외부라이브러리에서 내부 상태관리 시 성능 최적화
DOM 업데이트 타이밍
반응 상태 변경 시, DOM 자동 업데이트 그러나 DOM 업데이트는 동기적으로 적용되지 않음
대신 다음 업데이트 주기의 "다음 틱"까지 버퍼링해서 많은 상태 변경을 수행하던 각 컴포넌트가 한 번만 업데이트되도록 함
import { nextTick } from 'vue'
async function increment() {
count.value++
await nextTick()
// 이제 DOM이 업데이트되었습니다.
}
nextTick() - https://ko.vuejs.org/api/general.html#nexttick
reactive() - 반응 상태를 선언하는 다른 방법, 내부 값을 특수 객체로 감싸는 ref 와 달리 reactive 는 객체 자체를 반응형으로 만듬
import { reactive } from 'vue'
const state = reactive({ctn: 0});
//template
<button @click="state.ctn++">눌러보아라 이것이 reactive 다 ㅋ {{ state.ctn }}</button>
반응형 개체는 js Proxies 이며 일반 개체처럼 작동, 차이점은 Vue 가 반응성 추적 및 트리거링을 위해 반응성 객체의 모든 속성에 대한 액세스 및 변형을 가로챌 수 있다는 것.
반응형 재정의 vs 원본
유의 : reactive 의 반환 값은 원본 객체와 같지 않고 원본 객체를 재정의한 프록시라는 점 (react 느낌, ES6 문법 spread 느낌)
*프록시 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy
const raw = {}
const proxy = reactive(raw)
// 반응형으로 재정의 된 것은 원본과 같지 않습니다.
console.log(proxy === raw) // false
프록시만 반응형. 원본 객체를 변경해도 업데이트가 트리거 되지 않는다.
따라서 Vue 의 반응형 시스템으로 작업할 때 가장 좋은 방법 : 상태를 재정의한 프록시만 사용
console.log(proxy === reactive(raw)); // true
console.log(proxy === reactive(proxy)); // true
중첩된 객체에도 적용됨,
const proxy = reactive({})
const raw = {}
proxy.nested = raw
console.log(proxy.nested === raw) // false
reactive() 의 제한사항
1. 제한된 값 유형 : 객체 유형 (객체, 배열 및 컬렉션 유형에만 작동 ex_Map,Set) String, number, boolean 기본 유형보유 불가
2. 전체 객체를 대체할 수 없음 : 속성 액세스를 통해 작동하므로 동일한 참조를 항상 유지해야함! 대체불가능 (replace)
let state = reactive({ count: 0 })
// 위 참조({ count: 0 })는 더 이상 추적되지 않습니다.
// (반응성 연결이 끊어졌습니다!)
state = reactive({ count: 1 })
3. 분해 할당에 친화적이지 않음 : 반응형 객체의 타입 속성을 지역 변수로 분해하거나, 그 속성을 함수에 전달할 때 반응성 연결이 끊어짐
const state = reactive({ count: 0 })
// count는 분해 할당 될 때 state.count에서 연결이 끊어집니다.
let { count } = state
// 원래 상태에 영향을 주지 않음
count++
// 함수는 일반 숫자를 수신하고
// state.count에 대한 변경 사항을 추적할 수 없습니다.
// 반응성을 유지하려면 전체 개체를 전달해야 합니다.
callSomeFunction(state.count)
이런 제한때문에 기본 API로 ref() 를 사용하는게 정신건강에 좋다
추가적인 Ref 언래핑 세부 사항
ref는 반응 객체의 속성으로 액세스되거나 변경될 때 자동으로 래핑 해제됨
const count = ref(0)
const state = reactive({
count
})
console.log(state.count) // 0
state.count = 1
console.log(count.value) // 1
ref가 할당된 기존 속성에 새 ref를 할당하면 이전 ref는 대체됨
const otherCount = ref(2)
// 기존 ref는 이제 state.count에서 참조가 끊어집니다.
state.count = otherCount
console.log(state.count) // 2
console.log(count.value) // 1
* ref의 언래핑은 깊은 반응형 객체 내부에 중첩된 경우에만 발생, 얕은 반응형 객체의 속서으로 접근하는 경우에는 적용x
배열 및 컬렉션의 주의사항
반응형 객체와 달리 ref가 반응형 배열의 요소 또는 Map 과 같은 기본 컬렉션 유형으로 액세스 될 때 랩핑해제 수행x
const books = reactive([ref('Vue 3 Guide')])
// .value가 필요합니다
console.log(books[0].value)
const map = reactive(new Map([['count', ref(0)]]))
// .value가 필요합니다
console.log(map.get('count').value)
템플릿에서 래핑 해제시 주의사항
ref 언래핑은 ref가 템플릿 렌더링 컨텍스트의 최상위 속성인 경우에만 적용
예에서 count 및 object는 최상위 속성이지만 object.id는 그렇지 않음
const count = ref(0)
const object = { id: ref(1) }
{{ count + 1 }} // 이 표현식은 작동
{{ object.id + 1 }} // 작동 안함 [object Object]1 로 렌더링 됨
이 문제를 해결하기 위해 id를 최상위 속성으로 분해해야함
//js
const { id } = object
//tmp
{{ id + 1 }} // result : 2
주의사항 ref 텍스트 보간의 최종 평가 값은 경우 래핑x
{{ object.id }} // result : 1
https://v3-docs.vuejs-korea.org/guide/essentials/reactivity-fundamentals.html
Vue.js
Vue.js - The Progressive JavaScript Framework
vuejs.org