Vue 3 Reactivity Transform 2026: $ref, $computed 및 기술 면접 대비
Vue 3의 Reactivity Transform($ref, $computed)에 대해 심층적으로 분석합니다. 2026년 현재 상황, 폐기 이유, 현재 권장되는 대안, 그리고 기술 면접에서 자주 출제되는 질문들을 상세히 다룹니다.

Vue 3의 Reactivity Transform은 Composition API의 개발 경험을 개선하기 위해 도입된 실험적 기능입니다. $ref, $computed, $shallowRef 등의 매크로를 사용하여 .value 작성을 생략할 수 있는 문법적 편의를 제공했습니다. 하지만 Vue 3.3에서 이 기능은 폐기되었으며, 현재는 권장되지 않습니다. 기술 면접에서는 이 기능의 역사, 폐기 이유, 그리고 대안에 대한 이해가 요구됩니다.
Reactivity Transform은 Vue 3.3에서 폐기되었지만, @vue-macros/reactivity-transform 패키지를 사용하면 계속 사용할 수 있습니다. 다만, 신규 프로젝트에서의 사용은 권장되지 않습니다.
Reactivity Transform의 기본 개념과 역사
Reactivity Transform은 Vue 3.2에서 실험적 기능으로 도입되었습니다. 주요 목적은 ref를 사용할 때 매번 .value를 작성해야 하는 번거로움을 해소하는 것이었습니다. 컴파일 시점에 매크로를 일반 Vue 코드로 변환하는 방식입니다.
// Reactivity Transform을 사용한 경우 (폐기된 구문)
<script setup>
let count = $ref(0)
let doubled = $computed(() => count * 2)
function increment() {
count++
}
</script>
<template>
<button @click="increment">Count: {{ count }}</button>
<p>Doubled: {{ doubled }}</p>
</template>이 코드는 컴파일 시 다음과 같이 변환됩니다.
// 컴파일 후 실제 코드
<script setup>
import { ref, computed } from 'vue'
const count = ref(0)
const doubled = computed(() => count.value * 2)
function increment() {
count.value++
}
</script>
<template>
<button @click="increment">Count: {{ count }}</button>
<p>Doubled: {{ doubled }}</p>
</template>폐기된 이유와 기술적 배경
Vue 팀이 Reactivity Transform을 폐기한 이유는 여러 가지입니다. 기술 면접에서 이러한 이유에 대한 설명을 요청받을 수 있습니다.
1. 명시성과 예측 가능성 부족
Reactivity Transform은 변수가 반응형인지 여부를 구분하기 어렵게 만들었습니다. 일반 변수와 $ref로 선언된 변수는 시각적으로 구별이 어려워 코드 리뷰와 디버깅 시 혼란을 야기했습니다.
// 문제: 어떤 것이 반응형인지 한눈에 알기 어려움
let count = $ref(0) // 반응형
let total = 0 // 비반응형
function update() {
count++ // UI 업데이트됨
total++ // UI 업데이트되지 않음
}2. 도구 지원의 복잡성
IDE와 linter가 Reactivity Transform을 정확히 이해하려면 특별한 설정이나 플러그인이 필요했습니다. TypeScript의 타입 추론과의 통합도 완전하지 않았습니다.
// TypeScript 통합 문제
let items = $ref<string[]>([])
// 다음과 같은 작업에서 타입 오류가 발생할 수 있었음
items.push('new item') // 타입 추론이 제대로 작동하지 않는 케이스3. 반응성 손실 위험
함수에 전달하거나 구조 분해 할당 시 반응성이 손실되는 경우가 있었습니다. $$ 매크로로 회피할 수 있었지만, 학습 비용이 증가했습니다.
// 반응성 손실 예시
let count = $ref(0)
function useCounter(c) {
// c는 이미 원시값
console.log(c) // 0
}
useCounter(count) // 반응성 손실
// $$ 매크로를 사용한 회피 방법
useCounter($$(count)) // ref로 전달됨현재 권장되는 대안
Vue 3.4 이후로는 Reactivity Transform 대신 다음 접근 방식이 권장됩니다.
ref()의 표준 사용
.value 작성이 번거롭게 느껴질 수 있지만, 코드의 명시성과 예측 가능성이 향상됩니다.
<script setup>
import { ref, computed } from 'vue'
const count = ref(0)
const doubled = computed(() => count.value * 2)
function increment() {
count.value++
}
</script>
<template>
<button @click="increment">Count: {{ count }}</button>
<p>Doubled: {{ doubled }}</p>
</template>Composable 함수로 로직 추출
복잡한 반응형 로직은 Composable 함수로 추출하면 코드 재사용성과 유지보수성이 향상됩니다.
import { ref, computed } from 'vue'
export function useCounter(initialValue = 0) {
const count = ref(initialValue)
const doubled = computed(() => count.value * 2)
const isEven = computed(() => count.value % 2 === 0)
function increment() {
count.value++
}
function decrement() {
count.value--
}
function reset() {
count.value = initialValue
}
return {
count,
doubled,
isEven,
increment,
decrement,
reset
}
}reactive()와 toRefs() 조합
여러 관련 상태를 관리할 때 reactive()와 toRefs() 조합이 효과적입니다.
<script setup>
import { reactive, toRefs } from 'vue'
const state = reactive({
firstName: '',
lastName: '',
email: ''
})
const { firstName, lastName, email } = toRefs(state)
const fullName = computed(() => `${firstName.value} ${lastName.value}`)
</script>기술 면접 빈출 질문
Q1: ref와 reactive의 차이점은 무엇입니까?
import { ref, reactive, isRef, isReactive } from 'vue'
// ref는 원시값을 래핑
const count = ref(0)
console.log(isRef(count)) // true
console.log(count.value) // 0
// reactive는 객체를 프록시로 래핑
const state = reactive({ count: 0 })
console.log(isReactive(state)) // true
console.log(state.count) // 0 (.value 불필요)
// reactive 주의사항
const { count: destructuredCount } = state // 반응성 손실Q2: shallowRef와 ref의 차이점은 무엇입니까?
import { ref, shallowRef, triggerRef } from 'vue'
// ref는 깊은 반응성 제공
const deepRef = ref({ nested: { value: 1 } })
deepRef.value.nested.value = 2 // 변경 감지됨
// shallowRef는 얕은 반응성만 제공
const shallow = shallowRef({ nested: { value: 1 } })
shallow.value.nested.value = 2 // 변경 감지되지 않음
shallow.value = { nested: { value: 2 } } // 전체 교체는 감지됨
// 수동으로 트리거하는 경우
shallow.value.nested.value = 3
triggerRef(shallow) // 명시적으로 업데이트 알림Q3: watch와 watchEffect의 사용 구분은?
import { ref, watch, watchEffect } from 'vue'
const searchQuery = ref('')
const results = ref([])
// watchEffect: 의존성 자동 추적
watchEffect(async () => {
// searchQuery 변경 시 자동으로 재실행
if (searchQuery.value.length > 2) {
results.value = await fetchResults(searchQuery.value)
}
})
// watch: 명시적 의존성 지정
watch(searchQuery, async (newValue, oldValue) => {
console.log(`Changed from ${oldValue} to ${newValue}`)
if (newValue.length > 2) {
results.value = await fetchResults(newValue)
}
}, { immediate: false, deep: false })
// 여러 소스 감시
watch(
[searchQuery, () => state.filter],
([newQuery, newFilter], [oldQuery, oldFilter]) => {
// 두 변경 모두에 반응
}
)Vue.js / Nuxt.js 면접 준비가 되셨나요?
인터랙티브 시뮬레이터, flashcards, 기술 테스트로 연습하세요.
Q4: customRef는 어떤 상황에서 사용합니까?
import { customRef } from 'vue'
// 디바운스 적용된 ref 구현
function useDebouncedRef<T>(value: T, delay = 300) {
let timeout: ReturnType<typeof setTimeout>
return customRef<T>((track, trigger) => {
return {
get() {
track()
return value
},
set(newValue: T) {
clearTimeout(timeout)
timeout = setTimeout(() => {
value = newValue
trigger()
}, delay)
}
}
})
}
// 사용 예시
const searchInput = useDebouncedRef('', 500)Q5: toRef와 toRefs의 차이점을 설명해 주세요
import { reactive, toRef, toRefs } from 'vue'
const state = reactive({
firstName: 'John',
lastName: 'Doe',
age: 30
})
// toRef: 단일 속성을 ref로 변환
const ageRef = toRef(state, 'age')
ageRef.value++ // state.age도 31이 됨
// toRefs: 모든 속성을 refs로 변환
const { firstName, lastName } = toRefs(state)
firstName.value = 'Jane' // state.firstName도 'Jane'이 됨
// Composable에서의 활용
function useUserState() {
const state = reactive({ name: '', email: '' })
return toRefs(state) // 구조 분해해도 반응성 유지
}Vue 3.5 이후의 반응성 개선
Vue 3.5에서는 Reactivity Transform을 대체하는 새로운 개선 사항이 도입되었습니다.
Reactive Props Destructure
Vue 3.5에서는 defineProps에서 구조 분해해도 반응성이 유지됩니다.
<script setup>
// Vue 3.5 이후: 구조 분해해도 반응형 유지
const { title, count = 0 } = defineProps<{
title: string
count?: number
}>()
// watch로 감시 가능
watch(() => count, (newVal) => {
console.log('count changed:', newVal)
})
</script>
<template>
<h1>{{ title }}</h1>
<p>Count: {{ count }}</p>
</template>useTemplateRef
Vue 3.5에서는 템플릿 참조를 가져오기 위한 useTemplateRef 함수가 추가되었습니다.
<script setup>
import { useTemplateRef, onMounted } from 'vue'
const inputRef = useTemplateRef('input')
onMounted(() => {
inputRef.value?.focus()
})
</script>
<template>
<input ref="input" type="text" />
</template>성능 최적화 모범 사례
기술 면접에서는 반응성 시스템의 성능 최적화에 대해서도 질문받을 수 있습니다.
import { shallowRef, shallowReactive, markRaw, computed } from 'vue'
// 대용량 리스트에는 shallowRef 사용
const largeList = shallowRef<Item[]>([])
// 변경 불필요한 객체는 markRaw로 래핑
const config = markRaw({
apiUrl: 'https://api.example.com',
timeout: 5000
})
// computed의 캐시 활용
const expensiveComputed = computed(() => {
// 의존하는 값이 변경되지 않으면 재계산되지 않음
return largeList.value.filter(item => item.active).length
})
// 불필요한 반응성 피하기
const staticData = {
labels: ['A', 'B', 'C'], // 변경되지 않는 데이터
constants: Object.freeze({ MAX: 100 })
}결론
Vue 3의 Reactivity Transform은 개발 경험 향상을 목표로 한 야심찬 시도였지만, 명시성과 도구 지원 문제로 폐기되었습니다. 현재 Vue 개발에서는 표준 ref()와 computed()를 사용하고, 복잡한 로직은 Composable 함수로 추출하는 것이 권장됩니다.
기술 면접에서는 Reactivity Transform의 역사와 폐기 이유를 이해하는 것뿐만 아니라, ref, reactive, watch, computed 등 Core Reactivity API를 적절히 구분하여 사용할 수 있는 능력이 중요합니다. Vue 3.5에서 도입된 Reactive Props Destructure와 useTemplateRef 등 새로운 기능도 파악해 두면 더 높은 평가를 받을 수 있습니다.
Vue.js / Nuxt.js 코드의 버그를 찾을 수 있나요
실제 코드 한 조각, 숨은 버그 하나, 하루 한 번. 계정 없이 바로 도전할 수 있습니다.

작성자
Anthony Fillion-MailletSharpSkill 창업자
10년 이상 풀스택 개발을 해왔습니다. SharpSkill을 운영하며 이곳에 게시되는 모든 내용에 책임을 집니다.
2026년 9월 9일 업데이트
태그
공유
관련 기사

Vue 3 컴포저블 심화 가이드: 재사용 가능한 패턴과 기술 면접 질문 2026
Vue 3 고급 컴포저블 패턴을 체계적으로 분석합니다. 비동기 처리, 의존성 주입, 폼 유효성 검사, 테스트 전략, 그리고 2026년 기술 면접에서 자주 출제되는 질문과 답변을 다룹니다.

2026년 Vue 3 테스팅: Vitest, Vue Test Utils 그리고 면접 질문
2026년 Vue 테스팅 실전 가이드. Vitest 설정, Vue Test Utils로 컴포넌트 마운트, 컴포저블과 Pinia 스토어 테스트, API 모킹, 커버리지 측정, 그리고 채용 팀이 실제로 던지는 면접 질문까지 다룹니다.

Vue 3 Pinia vs Vuex 완벽 비교: 2026년 상태 관리 전략과 면접 핵심 질문
Vue 3 생태계에서 Pinia와 Vuex를 비교 분석합니다. Options Store와 Setup Store 패턴, TypeScript 통합, 크로스 스토어 구성, SSR 지원, Vuex에서 Pinia로의 마이그레이션 전략, 그리고 2026년 면접에서 자주 출제되는 상태 관리 질문을 코드 예제와 함께 정리합니다.