# ref 와 reactive는 어떻게 다른가

처음 만나는 Composition API 에서 setup 메소드를 이용하고, 데이터와 메소드를 리턴한 다음 <template> 에서 사용하는 컴포넌트를 살펴보았습니다.

그러나 데이터를 변경할 수는 없었습니다, this는 제공하지 않고, getCurrentInstance로 가져온 Vue 인스턴스인 vm 를 이용해도 데이터를 할 수 없었습니다.

Vue.js 3 부터는 반응성을 위한 두가지 유틸리티 ref reactive 두가지를 제공합니다. 우선, 선택하는 기준은 다음과 같습니다.

  • ref : 반응성이 필요한 데이터가 JavaScript 원시 자료형(primitive)인 경우
  • reactive : 반응성이 필요한 데이터가 원시 자료형이 아닌 경우. 보통 객체
  • 고르기 어려우면 항상 ref 를 사용해도 문제되지 않습니다.

# ref로 반응성을 추가한 message

<template>
  <div>{{ message }}</div>
  <button @click="reverse">뒤집기</button>
</template>
<script>
import { ref, getCurrentInstance } from 'vue'

export default {
  setup () {
    // 반응성을 가진 문자열로 만듭니다
    const message = ref('Hello World')
    const reverse = () => {
      message.value = message.value
        .split('')
        .reverse()
        .join('')
    }
    return { message, reverse }
  }
}
</script>

이제 "뒤집기" 버튼이 정상적으로 작동합니다. 메시지를 뒤집고 새로 message ref의 value에 넣어주면 <template> 에도 변경사항이 전달됩니다. ref 로 만든 객체는 아래와 같이 구성되어있습니다.

Object {
  __v_isRef: true,
  _rawValue: "Hello World",
  _shallow: false,
  _value: "Hello World"
}

value 가 변경되면 Vue.js 3 반응성 시스템에 따라 영향을 받는 모든 곳에 변경사항을 전파합니다. ref를 사용하는 경우 value 를 사용하는 것에 유의하세요. ref 외에도 깊은 반응성을 갖지 않는 shallowRef를 제공합니다. 이 장에서는 다루지 않습니다.

# reactive로 반응성을 추가한 message

message는 자바스크립트 원시 자료형인 문자열이기 때문에 reactive로 반응성을 추가할 수 없습니다. 아래 예제는 당연히 작동하지 않습니다.

<template>
  <div>{{ message }}</div>
  <button @click="reverse">뒤집기</button>
</template>
<script>
import { reactive, getCurrentInstance } from 'vue'

export default {
  setup () {
    // 원시 자료형은 반응성을 갖지 못합니다.
    const message = reactive('Hello World')
    const reverse = () => {
      message = message
        .split('')
        .reverse()
        .join('')
    }
    return { message, reverse }
  }
}
</script>

reactive를 이용해서 message에 반응성을 추가하려면 아래와 같이 사용하여야 합니다. 아래 방법은 이 예제 뿐만 아니라 <template> 태그에서 데이터와 메소드를 분리하여 확인할 수 있도록 명시적으로 변수를 선언하는 방법을 보여줍니다.

<template>
  <div>{{ state.message }}</div>
  <button @click="action.reverse">뒤집기</button>
</template>
<script>
import { reactive, getCurrentInstance } from 'vue'

export default {
  setup () {
    const state = reactive({
      message: 'Hello World'
    })
    const action = {
      reverse() {
        state.message = state.message.split('').reverse().join('')
      }
    }
    return { state, action }
  }
}
</script>

stateconsole.log로 출력하면 다음과 같이 나옵니다.

Object {
  message: "Hello World"
}

ref와 다르게 다른 속성이 없습니다. Vue.js 3는 reactive 로 반응성을 얻기 위해 Proxy (opens new window)Reflect (opens new window)를 사용합니다. Proxy는 target 객체와 handler를 이용해서 객체가 호출되거나 새로운 값이 지정될 때 다른 동작을 하도록 만들 수 있습니다. Vue.js 는 Reflect를 이용해 핸들러의 기본 동작을 Vue.js 의 메소드로 변경합니다.

reactive 핸들러는 Vue.js 코어의 baseHandler.tsmutableHandler 코드 (opens new window)부터 자세히 살펴볼 수 있습니다.

Vue.js 3 reactive 에서 사용하는 get 핸들러 createGetter (opens new window)set 핸들러 createSetter (opens new window) 도 살펴보세요

Vue.js 3의 refreactive 는 Vue.js 2 의 this만큼 많은 질문을 만들어낼 것으로 보입니다. 맨 위에 말씀드렸던 내용 중 "고르기 어려우면 항상 ref 를 사용해도 문제되지 않습니다." 의 이유는 다음과 같습니다.

ref 를 이용해서 객체를 만들면 반응성을 가진 객체를 만드는 시점에 reactive 로 감싸줍니다.

ref 의 생성자 중 convert 메소드의 구현 (opens new window) 을 살펴보세요.