GeehDev

[Vue3] 반응형 데이터 : ref() 와 reactive() 본문

Study/Vue.js

[Vue3] 반응형 데이터 : ref() 와 reactive()

geehyun 2025. 3. 15. 21:17

Index

    반응형

    [Vue3] 반응형 데이터 : ref() 와 reactive()


    반응형 데이터

    Vue에서 반응형 데이터(Reactivity) 란, 데이터가 변경될 때 자동으로 UI가 업데이트되는 데이터를 의미한다.
    데이터가 변하거나 특정 조건이 되었을 때 직접 UI에 이를 적용해주던 기존 방식보다 훨씬 간편하게 UI를 조작 및 관리할 수 있다.

    기존방식

    <p> 이름 : <strong id="userNm"></strong> </p>
    <input type="text" name="userNm" placeholder="이름을 입력하세요" /> 
    
    <script>
      document.querySelector('input[name=userNm]').eventListener('input', ()=>{
          document.getElementById('userNm').innerText = this.value;
      });
    </script>

    Vue의 반응형 데이터 방식

    <template>
        <p> 이름 : <strong id="userNm">{{ userNm }}</strong> </p>
        <input type="text" name="userNm" placeholder="이름을 입력하세요" v-model="userNm" /> 
    </template>
    
    <script setup lang="ts">
      import { ref } from "vue";
      const userNm = ref('');
    </script>

    Vue3에서는 이러한 반응형 데이터를 사용하는 대표적인 방법으로 ref(), reactive() 가 있다.

    ref()

    <template>
        <p>{{ count }}</p>
        <button @click="count.value++">+1</button>
    </template>
    
    <script setup lang="ts">
      import { ref } from "vue";
      const count = ref(0);
    </script>
    • reference 라는 의미로 반응형 데이터를 만들고, 해당 값을 참조할 수 있도록 해주는 함수
    • 기본형, 객체, 배열 등의 모든 자료형을 감쌀 수 있다.
    • 주로 기본형 타입을 담는데 유리하다.
    • 생성 후 값에 접근할 때는 변수.value로 접근해줘야한다.
    • 값 자체가 변경될 때, 객체 또는 배열에서 내부 값 또는 속성이 변경될 때 모두 감지할 수 있음

    reactive()

    <template>
        <p v-for="(item, id) of obj" :key="id">저의 {{id}} 은/는 {{item}} 입니다.</p>
    </template>
    
    <script setup lang="ts">
      import { reactive } from "vue";
      const obj = reactive({"이름": "geehdev", "직업": "developer", "취미": "game"});
    </script>
    • ref와 마찬가지로 반응형 데이터를 만드는 함수로, 배열, 객체 등 객체의 반응형 상태관리에 유리하다.
    • 생성 후 값에 접근할 때는 .value 없이 직접 접근이 가능하다.
    • 값 내부 속성이 변경될 때 업데이트를 감지하며, 값 자체가 변경될 때는 반응성 성질을 잃는다.

     

    ref() vs reactive()

    모두 vue에서 반응형 데이터를 만들고, 반응형 상태관리를 할 수 있지만, 사용목적과 동작방식에 따라 차이점이 있다.

    1. 사용 타입

    ref(), reactive() 모두 모든 타입을 대입할 수는 있으나, 업데이트 감지 범위와 반응성 유지 여부가 달라 각 각 주로 사용되는 타입이 다르다.

    • ref() : 기본타입을 대입에 사용된다. (값 자체의 변경을 감지하기 때문)
    • reactive() : 배열, 객체 타입을 대입에 주로 사용된다. (값 내부 속성의 변화를 감지하기 때문)

    예시

    ref()
    <!-- ref -->
    <script setup lang="ts">
        const ref1 = ref('ref 입니다.');
        const ref2 = ref(12345);
        const ref3 = ref({'key1' : 'value1', 'key2' : 'value2'});
        const ref4 = ref([1,2,3,4,5,6,7]);
    </script>
    
    <template>
        <strong>1. 기본형</strong>
        <p>ref1 = {{ref1}}</p>
        <p>ref2 = {{ref2}}</p>
        <strong>2. 객체</strong>
        <p>{{ ref3 }}</p>
        <p v-for="(value, key) in ref3">ref3[{{ key }}] = {{ value }}</p>
        <strong>3. 배열</strong>
        <p>{{ ref4 }}</p>
        <p v-for="(item, idx) in ref4">ref4[{{ idx }}] = {{ item }}</p>
    </template>

    ref()의 사용 타입 예시 화면

    reactive()
    <!-- reactive -->
    <script setup lang="ts">
        const reactive1 = reactive({"name" : "지현", "age" : 0, "arr": []});
        const reactive2 = reactive([1,2,3,4,5,6,7]);
        let reactive3 = reactive(0);        // 기본타입의 경우 대입은 가능하나, 값 변경될 시 감지 못함 => 반응형 데이터 특성을 잃음(아래 참고)
        let reactive4 = reactive("test");   // 기본타입의 경우 대입은 가능하나, 값 변경될 시 감지 못함 => 반응형 데이터 특성을 잃음(아래 참고)
    </script>
    
    <template>
        <p>{{ reactive1 }}</p>
        <p>{{ reactive2 }}</p>
        <p>{{ reactive3 }}</p>
        <p>{{ reactive4 }}</p>
    </template>

    Reactive()의 사용 타입 예시 화면

    2. 업데이트 감지 범위와 반응성 유지 여부

    • ref()
      1. 내부적으로 Object.defineProperty()를 사용하여 .value의 변경을 추적
      2. 대입된 값 자체의 변경을 감지한다.
    • reactive()
      1. Vue의 Proxy 기반으로 객체의 변화를 추적
      2. 대입된 값 내부 속성의 변화를 감지한다.
      3. 값 자체게 변경될 경우 반응성 성질을 잃고 변화를 감지하지 못하게된다. (기본형 타입에 사용하지 않는 이유)

    예시

    ref()
    <!-- ref -->
    <script setup lang="ts">
        let obj = ref({"name" : "지현", "age" : 0});
        const changeAgeOnly = (add:number) => {
          obj.value.age = obj.value.age + add;
        };
        const changeObj = (name:string, age:number) => {
          obj.value = ref({"newName" : name, "newAge" : age});
        }
    </script>
    
    <template>
        <p> obj = {{ obj }}</p>
        <strong>1. 값의 속성만 변경</strong>
        <button @click="changeAgeOnly(10)">나이 10살 더하기</button>	<!-- 업데이트 감지함 -->
        <strong>2. 값 자체를 변경</strong>
        <button @click="changeObj('새로운 지현', 200)">객체를 바꾸기</button> <!-- 업데이트 감지함 -->
    </template>

    ref() 업데이트 감지 범위 예시 - 값 내부 속성만 변경 시에도 업데이트 감지함.
    ref() 업데이트 감지 범위 예시 - 값 자체를 변경 시에도 업데이트 감지함.

     

    reactive()
    <!-- reactive -->
    <script setup lang="ts">
        const reactive1 = reactive({"name" : "지현", "age" : 0, "arr": []});
        const reactive2 = reactive([1,2,3,4,5,6,7]);
        let reactive3 = reactive(0);        // 기본타입의 경우 대입은 가능하나, 값 변경될 시 감지 못함 => 반응형 데이터 특성을 잃음(아래 참고)
        let reactive4 = reactive("test");   // 기본타입의 경우 대입은 가능하나, 값 변경될 시 감지 못함 => 반응형 데이터 특성을 잃음(아래 참고)
    
        const selectVar = ref('');
        const inputText = ref('');
        const changeReactive = (idx) => {
          if(idx === 1) {
            reactive1["name"] = '변경된 값';
            reactive1["age"] = '20';
            reactive1["etc"] = '기타';
            reactive1["arr"].push(1)
          }
          if(idx === 2) {
            reactive2.push(100);
          }
          if(idx === 3) {
            reactive3 = 'reactive3 변경';
          }
          if(idx === 4) {
            reactive4 = 'reactive4 변경';
          }
        };
    </script>
    
    <template>
        <p>{{ reactive1 }}</p>
        <p>{{ reactive2 }}</p>
        <p>{{ reactive3 }}</p>
        <p>{{ reactive4 }}</p>
        <button @click="changeReactive(1)">reactive1 값 변경</button>
        <button @click="changeReactive(2)">reactive2 값 변경</button>
        <button @click="changeReactive(3)">reactive3 값 변경</button>    <!-- 값이 변경되긴하나, 업데이트를 감지하지 못해 UI변화 없음 -->
        <button @click="changeReactive(4)">reactive4 값 변경</button>  <!-- 값이 변경되긴하나, 업데이트를 감지하지 못해 UI변화 없음 -->
    </template>

    reactive() 업데이트 감지 범위 예시 - 값 내부 속성 변경 시에 업데이트 감지함.
    reactive() 업데이트 감지 범위 예시 - 값 자체가 변경될 시엔은 업데이트 감지 못함.

    3. 데이터 접근 방식

    • ref()
      1. 기본적으로 값에 접근할 때는 .value로 접근한다.
      2. 단, template 내부에서는 Auto Unwrapping 으로 변수명으로 직접 접근한다.
    • reactive()
      1. .value 없이 직접 접근한다.

    예시

    ref()
    <script setup lang="ts">
        // ref : 3-1. template 내에서 값 접근 : Auto Unwrapping
        const count1 = ref(0);
    
        // ref : 3-2. template 외부 에서의 값 접근 : .value로 접근
        let count2 = ref(0);
        const add1UseValue = () => { count2.value++;};
        const add1NotUseValue = () => { count2++; };
    </script>
    
    <template>
        <!-- ref : 3-1. template 내에서 값 접근 : Auto Unwrapping -->
        <p>count = {{ count1 }}</p>
        <strong>1. 직접 접근</strong>
        <button @click="count1++">count1++</button>
        <strong>2. .value로 접근 => error 발생</strong>
        <button @click="count1.value++">count1.value++</button>
    
        <!-- ref : 3-2. template 외부 에서의 값 접근 : .value로 접근 -->
        <p>count = {{ count2 }}</p>
        <strong>1. 직접 접근 => 반응형 특성 잃음</strong>
        <button @click="add1NotUseValue()">add1NotUseValue</button>
        <strong>2. .value로 접근</strong>
        <button @click="add1UseValue()">add1UseValue</button>
    </template>

    ref() 데이터 접근 방식 예시 - template 내부 : 직접 접근 시 정상 작동
    ref() 데이터 접근 방식 예시 - template 내부 : .value 사용 시 에러 발생

     

    reactive()
    <script setup lang="ts">
    	 // reactive : 3. 어디서든 변수로 바로 접근
        const arr = reactive([1,2,3,4,5]);
        const addArr = ()=> {
            arr.push(arr.length + 1);
        }
    </script>
    
    <template>
    	<!-- reactive : 3. 어디서든 변수로 바로 접근  -->
        <p v-for="item of arr">{{ item }}</p>
        <button @click="addArr">arr push</button>
    </template>

    reactive() 데이터 접근 방식 예시 - 어디서든 변수명으로 바로 접근 가능

    4. 구조분해할당 처리 방식

    기본적으로 ref(), reactive() 로 할당된 반응형 데이터를 구조분해할당할 경우 구조분해할당된 변수에서는 기존 반응형 성질을 잃게된다.
    그러나 toRefs() 를 사용하여 구조분해할당한 변수도 ref 변수로 만들어 사용할 수 있다.

    예시

    ref()
    <script setup lang="ts">
        // ref : 4. 구조분해할당 사용
        let cnt = 0;
        const refObj = ref({"name" : "지현", "age" : 0});
        let {name: objName, age: objAge} = refObj.value;                // 일반 변수로, 반응형 성질 없음
        let {name: objName2, age: objAge2} = toRefs(refObj.value);        // toRef()를 사용하여 ref 변수로 생성
        const changeObjName = () => {
          refObj.value["name"] = refObj.value["name"] + cnt;
          refObj.value["age"] = refObj.value["age"] + cnt;
          cnt ++;
        };
    </script>
    
    <template>
        <!-- ref : 4. 구조분해할당 사용 -->
        <p>refObj : {{ refObj }}</p>
        <p>objName : {{ objName }}</p> <p>objAge : {{ objAge }}</p>            <!-- 일반 데이터로 refObj 변경될 시 같이 변경 안 됨 -->
        <p>objName2 : {{ objName2 }}</p> <p>objAge2 : {{ objAge2 }}</p>        <!-- 반응형 데이터로 refObj 변경될 시 같이 변경 됨 -->
        <button @click="changeObjName">refObj 값 변경</button>
    </template>

    ref() 에서 구조분해할당 사용예시 - 기본적으로는 반응형 유지 X / toRefs() 사용 시 반응형 유지 가능

     

    reactive()
    <script setup lang="ts">
        // reactive : 4. 구조분해할당 사용
        let cnt2 = 0;
        const reactiveObj = reactive({"name" : "지현", "age" : 0});
        const {name : reactiveObjName, age: reactiveObjAge} = reactiveObj;                // 일반 변수로, 반응형 성질 없음
        const {name : reactiveObjName2, age:  reactiveObjAge2} = toRefs(reactiveObj);    // toRef()를 사용하여 ref 변수로 생성
        const changeReactiveObjName = () => {
          reactiveObj["name"] = reactiveObj["name"] + cnt2;
          reactiveObj["age"] = reactiveObj["age"] + cnt2;
          cnt2++;
        };
    </script>
    
    <template>
        <!-- reactive : 4. 구조분해할당 사용 -->
        <p>reactiveObj : {{ reactiveObj }}</p>
        <p>reactiveObjName {{ reactiveObjName }}</p> <p>reactiveObjAge {{ reactiveObjAge }}</p>            <!-- 일반 데이터로 refObj 변경될 시 같이 변경 안 됨 -->
        <p>reactiveObjName2 {{ reactiveObjName2 }}</p> <p>reactiveObjAge2 {{ reactiveObjAge2 }}</p>        <!-- 반응형 데이터로 refObj 변경될 시 같이 변경 됨 -->
        <button @click="changeReactiveObjName">reactiveObj 속성 값 변경</button>    
    </template>

    reactive() 에서 구조분해할당 사용예시 - 기본적으로는 반응형 유지 X / toRefs() 사용 시 반응형 유지 가능

    요약

    항목 ref() reactive()
    주 사용 타입 원시 타입 (string, number, boolean) 객체, 배열
    반응성 감지 방식 .value의 변경을 감지
    (Object.defineProperty 사용)
    Proxy 기반으로 내부 속성 변경을 감지
    값 변경 감지 여부 값 자체의 변경을 감지 내부 속성 변경을 감지하지만, 객체 자체를 변경하면 반응성을 잃음
    템플릿 내부에서 접근 방식 - 변수명으로 직접 접근
    - .value로 접근 시 에러 발생
    {{ 변수명 }}
    (Auto Unwrapping 지원)
    변수명으로 직접 접근
    ex : {{ 객체명 }}, {{ 객체명.속성 }}
    템플릿 외부에서 접근 방식 .value로 접근해야 함 (refVar.value) .value 없이 직접 접근 가능
    배열, 객체 사용 여부 가능하지만 .value 필요 .value 없이 바로 사용 가능
    구조 분해 시 반응성 유지 여부 const { x } = refObj.value; → ❌ 반응성 깨짐
    const { x } = toRefs(refObj.value); → ✅ 반응성 유지됨

     

    마치며

    vue에서 가장 강력한 기능인 반응형 데이터를 사용하는 기초적인 방식에 대해서 공부하고 정리해봤다.

    정리하면서 ref와 reactive를 사용할 때 각각 정확히 어떤 상황에서 사용하는 것이 유리한 가에 초점을 두고 알아봤는데, ref의 경우 비교적 간단하게 기본형 타입에 대해 값을 바인딩 해줄때 사용하고, reacive는 객체, 배열을 담아서 사용할 때 유리하다고 생각된다.

    728x90

    'Study > Vue.js' 카테고리의 다른 글

    [Vue3] Vue 템플릿 문법 (Mustache, Directive)  (0) 2025.03.02
    [Vue3] Vue.js 소개 및 개발환경 셋팅  (0) 2025.02.24