5 min read

Vue의 ref, shallowRef, reactive, shallowReactive 차이

Table of Contents

λ“€μ–΄κ°€λ©°

Vue 3의 Composition APIλ₯Ό μ‚¬μš©ν•˜λ‹€ 보면 λ°˜μ‘ν˜• 데이터λ₯Ό λ§Œλ“œλŠ” 방법이 μ—¬λŸ¬ 가지라 ν—·κ°ˆλ¦΄ λ•Œκ°€ μžˆμŠ΅λ‹ˆλ‹€. ref, shallowRef, reactive, shallowReactive… 이름은 λΉ„μŠ·ν•œλ° λ™μž‘μ€ λ―Έλ¬˜ν•˜κ²Œ λ‹€λ¦…λ‹ˆλ‹€.

이 κΈ€μ—μ„œλŠ” λ„€ κ°€μ§€ API의 차이λ₯Ό μ½”λ“œμ™€ ν•¨κ»˜ μ •λ¦¬ν•΄λ³΄κ² μŠ΅λ‹ˆλ‹€.


ref vs reactive

κ°€μž₯ 기본적인 두 κ°€μ§€λΆ€ν„° λΉ„κ΅ν•΄λ΄…μ‹œλ‹€.

ref

refλŠ” μ–΄λ–€ νƒ€μž…μ˜ 값이든 λ°˜μ‘ν˜•μœΌλ‘œ λ§Œλ“€ 수 μžˆμŠ΅λ‹ˆλ‹€. μ›μ‹œκ°’(string, number, boolean)은 λ¬Όλ‘  객체도 κ°€λŠ₯ν•©λ‹ˆλ‹€.

<script setup>
import { ref } from "vue";

const count = ref(0);
const user = ref({ name: "Kwan", age: 30 });

// .value둜 μ ‘κ·Ό
count.value++;
user.value.name = "Lee"; // κΉŠμ€ 속성 변경도 λ°˜μ‘ν˜•μœΌλ‘œ 감지
</script>

refλŠ” .value둜 감싸진 객체λ₯Ό λ°˜ν™˜ν•©λ‹ˆλ‹€. 값에 μ ‘κ·Όν•˜κ±°λ‚˜ λ³€κ²½ν•  λ•Œ 항상 .valueλ₯Ό 거쳐야 ν•©λ‹ˆλ‹€. 단, ν…œν”Œλ¦Ώμ—μ„œλŠ” μžλ™μœΌλ‘œ μ–Έλž˜ν•‘λ˜μ–΄ .value 없이 μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

객체λ₯Ό ref에 λ„£μœΌλ©΄ λ‚΄λΆ€μ μœΌλ‘œ reactiveλ₯Ό μ‚¬μš©ν•˜μ—¬ κΉŠμ€(deep) λ°˜μ‘ν˜•μ΄ μ μš©λ©λ‹ˆλ‹€. μ€‘μ²©λœ 속성을 변경해도 κ°μ§€λ©λ‹ˆλ‹€.

reactive

reactiveλŠ” 객체 νƒ€μž…(Object, Array, Map, Set λ“±) 만 λ°˜μ‘ν˜•μœΌλ‘œ λ§Œλ“€ 수 μžˆμŠ΅λ‹ˆλ‹€. μ›μ‹œκ°’μ€ μ‚¬μš©ν•  수 μ—†μŠ΅λ‹ˆλ‹€.

<script setup>
import { reactive } from "vue";

const user = reactive({ name: "Kwan", age: 30 });

// .value 없이 직접 μ ‘κ·Ό
user.name = "Lee"; // λ°˜μ‘ν˜•μœΌλ‘œ 감지
</script>

.value 없이 직접 속성에 μ ‘κ·Όν•  수 μžˆλ‹€λŠ” 점이 νŽΈλ¦¬ν•˜μ§€λ§Œ, λͺ‡ κ°€μ§€ μ œμ•½μ΄ μžˆμŠ΅λ‹ˆλ‹€.

// 1. μ›μ‹œκ°’ μ‚¬μš© λΆˆκ°€
const count = reactive(0); // λ™μž‘ν•˜μ§€ μ•ŠμŒ

// 2. κ΅¬μ‘°λΆ„ν•΄ν•˜λ©΄ λ°˜μ‘μ„±μ„ μžƒμŒ
const { name, age } = reactive({ name: "Kwan", age: 30 });
// name, ageλŠ” 더 이상 λ°˜μ‘ν˜•μ΄ μ•„λ‹˜

// 3. μž¬ν• λ‹Ήν•˜λ©΄ λ°˜μ‘μ„±μ„ μžƒμŒ
let user = reactive({ name: "Kwan" });
user = reactive({ name: "Lee" }); // κΈ°μ‘΄ 참쑰와 μ—°κ²° λŠκΉ€

정리

ꡬ뢄refreactive
μ‚¬μš© κ°€λŠ₯ν•œ νƒ€μž…λͺ¨λ“  νƒ€μž…κ°μ²΄ νƒ€μž…λ§Œ
μ ‘κ·Ό 방식.value ν•„μš”μ§μ ‘ μ ‘κ·Ό
ꡬ쑰뢄해toRefs() μ‚¬μš© μ‹œ μœ μ§€λ°˜μ‘μ„± μžƒμŒ
μž¬ν• λ‹Ή.value둜 κ°€λŠ₯λ°˜μ‘μ„± μžƒμŒ
κΉŠμ€ λ°˜μ‘ν˜•O (객체일 λ•Œ)O

Vue 곡식 λ¬Έμ„œμ—μ„œλ„ refλ₯Ό 기본으둜 μ‚¬μš©ν•˜λŠ” 것을 ꢌμž₯ν•©λ‹ˆλ‹€. reactive의 μ œμ•½ 사항듀 λ•Œλ¬Έμ— μ˜ˆμƒμΉ˜ λͺ»ν•œ 버그λ₯Ό λ§Œλ“€κΈ° 쉽기 λ•Œλ¬Έμž…λ‹ˆλ‹€.


shallowRef vs shallowReactive

ref와 reactiveλŠ” λͺ¨λ‘ κΉŠμ€(deep) λ°˜μ‘ν˜•μž…λ‹ˆλ‹€. μ€‘μ²©λœ 객체의 μ–΄λ–€ 속성을 바꿔도 κ°μ§€ν•©λ‹ˆλ‹€. ν•˜μ§€λ§Œ 큰 κ°μ²΄μ—μ„œ κΉŠμ€ λ°˜μ‘ν˜•μ΄ λΆˆν•„μš”ν•˜λ‹€λ©΄ μ„±λŠ₯상 뢀담이 될 수 μžˆμŠ΅λ‹ˆλ‹€.

μ΄λ•Œ μ‚¬μš©ν•˜λŠ” 것이 shallowRef와 shallowReactiveμž…λ‹ˆλ‹€. 얕은(shallow) λ°˜μ‘ν˜•μœΌλ‘œ, μ΅œμƒμœ„ 레벨만 κ°μ§€ν•©λ‹ˆλ‹€.

shallowRef

<script setup>
import { shallowRef } from "vue";

const user = shallowRef({ name: "Kwan", age: 30 });

// .value 자체λ₯Ό κ΅μ²΄ν•˜λ©΄ λ°˜μ‘ν˜•μœΌλ‘œ 감지
user.value = { name: "Lee", age: 25 }; // 감지됨

// κΉŠμ€ 속성 변경은 κ°μ§€λ˜μ§€ μ•ŠμŒ
user.value.name = "Park"; // 감지 μ•ˆ 됨 (UI μ—…λ°μ΄νŠΈ μ—†μŒ)
</script>

shallowRefλŠ” .value의 ꡐ체만 μΆ”μ ν•©λ‹ˆλ‹€. λ‚΄λΆ€ 속성을 직접 λ°”κΎΈλ©΄ Vueκ°€ μ•Œμ•„μ±„μ§€ λͺ»ν•©λ‹ˆλ‹€.

그런데 μ›μ‹œκ°’(number, string λ“±)을 shallowRef에 λ„£μœΌλ©΄ μ–΄λ–¨κΉŒμš”? μ›μ‹œκ°’μ€ μ• μ΄ˆμ— 쀑첩 속성이 μ—†κΈ° λ•Œλ¬Έμ— .value ꡐ체가 κ³§ κ°’ λ³€κ²½μž…λ‹ˆλ‹€. 즉, κΉŠμ€ λ°˜μ‘ν˜• λ³€ν™˜μ΄ λ°œμƒν•  μ—¬μ§€κ°€ μ—†μ–΄ ref보닀 λΆˆν•„μš”ν•œ μ˜€λ²„ν—€λ“œ 없이 λ™μž‘ν•©λ‹ˆλ‹€.

import { shallowRef } from 'vue'

const count = shallowRef(0)

count.value = 10 // 감지됨 (μ›μ‹œκ°’μ€ 항상 .value κ΅μ²΄μ΄λ―€λ‘œ μžμ—°μŠ€λŸ½κ²Œ λ™μž‘)

λ‹¨μˆœν•œ μ›μ‹œκ°’ μƒνƒœλ₯Ό λ‹€λ£° λ•Œ shallowRefλ₯Ό μ‚¬μš©ν•˜λ©΄, 값이 객체둜 ν™•μž₯λ˜λ”λΌλ„ μ˜λ„μΉ˜ μ•Šμ€ κΉŠμ€ λ°˜μ‘ν˜• λ³€ν™˜μ„ λ°©μ§€ν•  수 μžˆμ–΄ μ„±λŠ₯ λ©΄μ—μ„œ μœ λ¦¬ν•©λ‹ˆλ‹€.

κΉŠμ€ 속성을 λ³€κ²½ν•œ λ’€ κ°•μ œλ‘œ λ°˜μ‘μ‹œν‚€κ³  μ‹Άλ‹€λ©΄ triggerRef()λ₯Ό μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

import { shallowRef, triggerRef } from "vue";

const user = shallowRef({ name: "Kwan" });

user.value.name = "Lee"; // μ΄κ²ƒλ§ŒμœΌλ‘œλŠ” UI μ—…λ°μ΄νŠΈ μ•ˆ 됨
triggerRef(user); // μˆ˜λ™μœΌλ‘œ νŠΈλ¦¬κ±°ν•˜λ©΄ UI μ—…λ°μ΄νŠΈ

shallowReactive

<script setup>
import { shallowReactive } from "vue";

const state = shallowReactive({
  name: "Kwan",
  nested: { count: 0 },
});

// μ΅œμƒμœ„ 속성 변경은 감지
state.name = "Lee"; // 감지됨

// 쀑첩 속성 변경은 감지 μ•ˆ 됨
state.nested.count++; // 감지 μ•ˆ 됨 (UI μ—…λ°μ΄νŠΈ μ—†μŒ)
</script>

shallowReactiveλŠ” 객체의 1depth μ†μ„±λ§Œ λ°˜μ‘ν˜•μœΌλ‘œ μΆ”μ ν•©λ‹ˆλ‹€. μ€‘μ²©λœ 객체의 변경은 κ°μ§€ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.


μ–Έμ œ shallowλ₯Ό μ“ΈκΉŒ?

λŒ€λΆ€λΆ„μ˜ κ²½μš°μ—λŠ” ref와 reactive둜 μΆ©λΆ„ν•©λ‹ˆλ‹€. shallow 버전은 λ‹€μŒκ³Ό 같은 μƒν™©μ—μ„œ κ³ λ €ν•˜λ©΄ μ’‹μŠ΅λ‹ˆλ‹€.

  • 큰 λ¦¬μŠ€νŠΈλ‚˜ λ³΅μž‘ν•œ 객체λ₯Ό λ‹€λ£¨λŠ”λ°, λ‚΄λΆ€ μ†μ„±μ˜ 변경을 일일이 좔적할 ν•„μš”κ°€ 없을 λ•Œ
  • μ™ΈλΆ€ 라이브러리의 객체λ₯Ό λ°˜μ‘ν˜•μœΌλ‘œ 감싸야 ν•˜μ§€λ§Œ, λ‚΄λΆ€ κ΅¬μ‘°κΉŒμ§€ ν”„λ‘μ‹œλ‘œ λ§Œλ“€κ³  μ‹Άμ§€ μ•Šμ„ λ•Œ
  • μ„±λŠ₯ μ΅œμ ν™”κ°€ ν•„μš”ν•œ 경우 (κΉŠμ€ λ°˜μ‘ν˜•μ€ μ€‘μ²©λœ λͺ¨λ“  속성에 ν”„λ‘μ‹œλ₯Ό 생성)

μ‹€μ œλ‘œ Vue의 λ‚΄λΆ€ μ½”λ“œμ—μ„œλ„ μ»΄ν¬λ„ŒνŠΈ μΈμŠ€ν„΄μŠ€λ₯Ό shallowReactive둜 κ΄€λ¦¬ν•©λ‹ˆλ‹€. λͺ¨λ“  λ‚΄λΆ€ μ†μ„±κΉŒμ§€ 깊게 좔적할 ν•„μš”κ°€ μ—†κΈ° λ•Œλ¬Έμ΄μ£ .


마치며

λ„€ κ°€μ§€ APIλ₯Ό ν•œ μ€„λ‘œ μ •λ¦¬ν•˜λ©΄ μ΄λ ‡μŠ΅λ‹ˆλ‹€.

APIλŒ€μƒλ°˜μ‘ν˜• κΉŠμ΄μ ‘κ·Ό 방식
refλͺ¨λ“  νƒ€μž…κΉŠμ€(deep).value
shallowRefλͺ¨λ“  νƒ€μž…μ–•μ€(shallow).value
reactive객체 νƒ€μž…κΉŠμ€(deep)직접 μ ‘κ·Ό
shallowReactive객체 νƒ€μž…μ–•μ€(shallow)직접 μ ‘κ·Ό

κΈ°λ³Έμ μœΌλ‘œλŠ” refλ₯Ό μ‚¬μš©ν•˜κ³ , μ„±λŠ₯ μ΅œμ ν™”κ°€ ν•„μš”ν•œ νŠΉμˆ˜ν•œ κ²½μš°μ—λ§Œ shallow 버전을 κ³ λ €ν•˜λŠ” 것이 μ’‹κ² μŠ΅λ‹ˆλ‹€. reactiveλŠ” .value 없이 μ‚¬μš©ν•  수 μžˆμ–΄ νŽΈλ¦¬ν•˜μ§€λ§Œ κ΅¬μ‘°λΆ„ν•΄λ‚˜ μž¬ν• λ‹Ή μ‹œ λ°˜μ‘μ„±μ„ μžƒλŠ” μ œμ•½μ΄ μžˆμœΌλ‹ˆ μ£Όμ˜ν•΄μ„œ μ‚¬μš©ν•©μ‹œλ‹€.


참고 자료