선언적인 반응형 상태
reactive() 함수로 반응형 객체나 반응형 배열을 만들 수 있다.
import { reactive } from ' vue'
const state = reactive({ count: 0 })
반응형 객체는 일반 객체와 같이 동작하며 자바스크립트 프록시 이다. 차이점은 뷰는 반응형 객체의 상태변화와 프로퍼티 접근을 추적하는 것이 가능하다. 더 자세히 알고 싶다면, Reactivity in Depth 섹션에서 뷰의 반응형 시스템 동작을 설명해두었다. - 하지만 메인 가이드를 모두 끝내고 읽는것을 추천한다.
컴포넌트 템플릿에서 반응형 상태를 사용하기 위해, 컴포넌트의 setup() 함수로 선언하고 리턴해야 한다.
import [ reactive } from 'vue'
export default {
// 'setup' 은 composition API를 위한 특별한 hook 지시자 이다.
setup {
const state = reactive({ count: 0 })
// template으로 상태를 보내기
return {
state
}
}
}
//template
<div> {{ state.count }}</div>
비슷하게, 같은 범위에 반응형 상태를 변화시키는 함수를 선언하고, 그 함수를 상태 옆에 메소드로 내보낼 수 있다.
import [ reactive } from 'vue'
export default {
setup {
const state = reactive({ count: 0 })
function increment() {
state.count++
}
// don't forget to expose the function as well.
return {
state,
increment
}
}
}
내보낸 메소드는 일반적으로, 이벤트 리스너로 사용된다.
<button @click="increment">
{{ state.count }}
</button>
- <script setup>
setup()을 통해 메소드와 상태를 내보내는 것은 장황할 수 있다. 운좋게, 이렇게 내보내는 것은 오직 빌드스텝을 거ㅣ지 않을 때만 필요한 과정이다. 싱글파일컴포넌트를 사용할 때에는, <script setup>으로 사용법을 대단히 간소화할 수 있다.
<script setup>
import reactive } from 'vue'
const state = reactive({ count: })
function increment() {
state.count++
}
</script>
<template>
<button @click="increment">
{{ state.count }}
</button>
</template>
<script setup>에서 선언되는 변수와 최상위import들은 자동적으로 같은 컴포넌트의 템플릿에서 사용가능해진다.
이 가이드의 나머지부분에서, 뷰 개발자들을 위한 가장 보편적인 사용법으로 Compositions API 코드 예제를 위해 SFC와 <script setup> 구문이 주요하게 사용될 것이다.
- DOM 업데이트 타이밍
반응형 상태를 변화시킬 때, 돔은 자동적으로 업데이트 된다. 하지만, 돔업데이트는 동기화하여 적용되지 않는걸 주의해야한다. 대신에 뷰는, 업데이트 주기의 "next tick(다음 체크 표시)"까지 버퍼를하여, 각각의 컴포넌트가 얼마나 여러번 상태변화했는지 관계없이 오직 한 번만 업데이트한다.
상태변화 이후 돔업데이트가 완료되기까지 기다리기 위해, nextTick()이라는 전역 API를 사용할 수 있다.
import { nextTick} from 'vue'
function increment() {
state.count++
nextTick(() => {
// access updated DOM
})
}
- 깊은 반응형
뷰에서, 상태는 기본적으로 깊은 반응형이다. 내장 객체나 배열의 상태변화를 했을때 조차 감지되고 변경된다는 것이다.
import { reactive } from 'vue'
const obj = reactive({
nested: { count: 0 },
arr: ['foo', 'bar']
})
function mutateDepply() {
// 기대처럼 동작한다.
obj.nested.count++
obj.arr.push('baz')
}
반응형이 루트레벨에서만 추적되는 얕은 반응형 객체를 명시적으로 생성하는것 또한 가능하다. 하지만 일반적으로 특별한 경우에만 필요하다.
- 반응형 프록시 vs. 오리지널
reactive()에서 리턴되는 값은 오리지널객체와는 다른, 오리지널객체의 프록시이다.
const raw = {}
const proxy = reactive(raw)
// proxy 는 original과 다름
console.log(proxy === raw) // false
프록시만 반응형이다. 오리지널객체의 상태변화는 업데이트를 트리거 하지 않는다. 그러므로, 뷰의 반응형 시스템으로 작업할 때 모범사례는 상태의 프록시 버전만 독점적으로 사용하는 것이다.
프록시에만 항상 접근하기 위해, 같은 객체를 reactive()로 호출하면 항상 같은 프록시를 리턴하고, 존재하는 프록시를 reactive()로 호출하는것 또한 같은 프록시를 리턴한다.
const raw = {}
const proxy = reactive(raw)
// 같은 객체에 reactive()를 호출하는 것은 같은 프록시를 리턴한다.
console.log(reactive(raw) === proxy) // true
// 프록시에 reactive()를 호출하면 스스로를 리턴한다.
console.log(reactive(proxy) === proxy) // true
이 룰은 nested object에도 똑같이 적용된다. 깊은 반응성 덕에, nested object는 안에있는 object도 프록시다.
이 룰은 중첩 객체에도 적용된다. 깊은 반응형으로 인해, 중첩 객체의 내부 반응형 객체는 또한 프록시다.
const raw = {}
const proxy = reactive({})
proxy.nested = raw
console.log(proxy.nested === raw) // false
- reactive()의 한계
reactive() API는 두 가지 제한사항이 있다.
1. 객체 타입에만 동작한다. (객체, 배열, Map이나 Set같은 컬렉션) string, number, boolean같은 원시타입에는 사용할 수 없다.
2. 뷰의 반응형 추적이 프로퍼티 접근에 동작하기 때문에, 반응형 객체에 같은 참조를 항상 유지하게 된다. 반응형 객체를 쉽게 바꿀수 없다는 것을 뜻한다. 왜냐하면, 첫번째 참조에대한 반응형 연결을 잃었기 때문이다.
let state = reactive({ count: 0 })
// ({count: 0})에 대한 참조는 더이상 추적될 수 없다.(반응형 연결이 사라졌다.)
state = reactive({ count: 1 })
우리가 지역변수에 반응형 객체의 프로퍼티를 배정하거나 구조분할(destructure)했을 때, 또는 함수에 프로퍼티를 넘겼을 때, 반응형 연결을 잃게된다.
const state = reactive({ count: 0 })
// 지역변수에 할당하면 reactive 속성을 잃게된다.(반응성을 잃게된다)
let n = state.count
// state.count의 값은 변하지 않는다.
n++
console.log(n); // 1
console.log(state.count); // 0
const state = reactive({ count: 0 })
// count를 구조분할 할당해도 state.count와는 연결이 끊김.
let { count } = state
// state에는 영향을 주지 못함
count++
console.log(conunt); // 1
console.log(state.count); // 0
const state = reactive({ count:0 })
function plusCount(count){
count++
}
// 함수는 plain number를 받는다.
// state.count를 추적해서 값을 바꾸지 못한다.
plusCount(state.count)
console.log(state.count) // 0
ref()와 반응형 변수
reactive()의 한계를 해결하기 위해, 뷰는 어떤 값 타입이든 갖고 있을 수 있는 반응형 "refs"를 만드는 ref() 함수를 제공한다.
import { ref } from 'vue'
const count = ref(0)
ref()는 아규먼트를 받아서, ref 오브젝트안에 .value 프로퍼티로 래핑한 것을 리턴한다.
const count = ref(0)
console.log(count) // {value: 0}
console.log(count.value) // 0
count.value++
console.log(count.value) // 1
- 타입스크립트
타입스크립트에서는 이렇게 사용할 수 있다.
import { ref } from 'vue'
import type { Ref } from 'vue'
const year: Ref<string | number> = ref('2020')
year.value = 2020 // ok!
const year = ref<string | number>('2020')
year.value = 2020 // ok!
그리고, 초기값을 안써주면, undefined가 자동으로 추가 된다.
// inferred type: Ref<number | undefined>
const n = ref<number>()
반응형 객체의 프로퍼티들과 비슷하게, ref의 .value프로퍼티는 반응형이다. 게다가, 객체의 타입을 가질 때, ref는 .value를 reactive()로 자동으로 변환한다.
객체 값을 탐고있는 ref는 전체 객체가 반응형으로 변경될 수 있다.
const objectRef = ref({ count: 0})
objectRef.value = {count: 1}
// 반응형으로 동작함
console.log(obejctRef.value); // Proxy {count: 1}
refs는 반응을 잃지 않고 함수나 구조분할에 순수한 객체를 넘길수 있다.
const obj = {
foo: ref(1),
bar: ref(2)
}
// 함수는 ref를 받는다.
// .value를 통해 value에 접근하는 것이 필요하다.
// 반응형 연결을 유지할 것이다.
callSomeFunction(obj.foo)
// 여전히 반응형이다
const { foo, bar } = obj
다시 말하면, ref()는 어떤 값이든 "참조"를 만들 수 있고, 반응형을 잃지않고 참조를 넘길 수 있다. 이 능력은 구성가능한 함수 (Composable Functions)로 로직을 추출할때 매우 빈번히 사용되므로 매우 중요하다.
- Ref Unwrapping in Templates
refs는 템플릿에서 최상위 프로퍼티로서 접근될때, .value를 사용할 필요없이 자동적으로 "언래핑(unwrapped)"된다. 이전의 count 예제에 ref()를 사용한 예제이다.
<script setup>
import { ref } from 'vue'
const count = ref(0)
function increment() {
count.value++
}
</script>
<template>
<button @click="increment">
{{ count }} <!-- .value를 사용하지 않아도 된다 -->
</button>
</template>
언래핑은 템플릿을 렌더링한 문서(template render context)의 최상위 프로퍼티가 ref일때만 적용된다. 예를 들면, foo는 최상위 프로퍼티지만, object.foo는 아니다.
그래서 다음과 같은 객체가 주어진다:
const object = { foo: ref(1) }
다음과 같은 표현식은 기대처럼 동작하지 않는다.
{{ object.foo + 1 }}
렌더링 결과는 [object Object]이다. 왜냐하면, object.foo는 ref 객체기 때문이다. foo를 최상위 프로퍼티로 만드는 것으로 고칠 수 있다.
const object = { foo: ref(1) }
const { foo } = obejct
// in template
{{ foo + 1 }}
이제 렌더링 결과는 2이다.
기억해야 할것은 ref가 문자 보간법({{ }} 태그같은)의 마지막 계산된 값이면 또한 언래핑 된다는 것이다. 그래서 다음은 1로 렌더링 될 것이다.
// in script
const object = { foo: ref(1) }
// in template
{{ object.foo }}
이건 문자 보간법의 편리한 기능일 뿐이며, {{ object.foo.value }}와 동등하다.
- 반응형 객체에서 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로 연결된 프로퍼티를 배정하면, old ref를 교체할 것이다.
const count = ref(0)
const state = reactive({
count
})
const otherCount = ref(2)
state.count = otherCount
console.log(state.count) // 2
// 원래의 ref는 state.count와는 연결이 끊어졌다.
console.log(count.value) // 0
Ref 언래핑은 깊은 반응형 객체가 중첩되었을 때만 일어난다. 얕은 반응형 객체의 프로퍼티로의 접근은 적용되지 않는다.
- 배열과 컬렉션에서 Ref 언래핑
반응형 객체와 다르게, ref가 Map과 같은 순수 컬렉션 타입이나 반응형 객체의 원소에 접근할 때는, 언래핑이 일어나지 않는다.
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)
반응형 변형 (실험적인 기능 experimental)
ref와 .value를 같이쓰는 것은 자바스크립트의 언어적 제약에 의해 부과된 불이익이다. 하지만, 컴파일 타임 변형으로 우리는 적절한 위치에 자동으로 .value 붙임으로 사용감을 개선할 수 있다. 뷰는 전에 사용한 "count"예제와 같이 사용하도록 컴파일 타임 변형을 제공해준다.
<script setup>
let count = $ref(0)
function increment() {
// no need for .value
count++
}
</script>
<template>
<button @click="increment">{{ count }}</button>
</template>
전용 섹션인 반응형 변형에서 더 알아볼 수 있다. 이건 현재 실험적인 기능이고, 최종 결정 전에 변경될 수 있다.
'front > vue' 카테고리의 다른 글
[vue3 공식문서 번역]Essentials.5.Class and Style Bindings (0) | 2022.04.21 |
---|---|
[vue3 공식문서 번역]Essentials.4.Computed Properties (0) | 2022.04.21 |
[vue3 공식문서 번역]Essentials.2.Template Syntax (0) | 2022.04.21 |
Attribute와 Property의 차이 (0) | 2022.04.21 |
[vue3 공식문서 번역]Essentials.1.Creating an Appliction (0) | 2022.04.21 |
댓글