Vue 3 반응성 파헤치기 - ref, reactive, effect 심층 분석
Emily Parker
Product Engineer · Leapcell

소개
현대 웹 개발의 세계에서 동적이고 반응성이 좋은 사용자 인터페이스를 만드는 것은 매우 중요합니다. Vue.js와 같은 프레임워크는 애플리케이션 상태를 관리하고 업데이트를 효율적으로 렌더링하는 강력한 도구를 제공하여 이 프로세스를 크게 단순화했습니다. Vue 3의 우아함의 핵심에는 정제된 반응성 시스템이 있으며, 이는 의존성을 자동으로 추적하고 필요한 경우에만 컴포넌트를 다시 렌더링하는 정교한 메커니즘입니다. 이 시스템을 이해하는 것은 성능이 뛰어나고 유지보수 가능하며 예측 가능한 Vue 애플리케이션을 작성하는 데 중요합니다. 이 글에서는 Vue 3 반응성 시스템의 기본 원리를 철저히 탐구하고 기본 구성 요소인 ref
, reactive
, effect
에 중점을 둘 것입니다.
반응성의 핵심 개념
특정 API를 자세히 살펴보기 전에 Vue 3 반응성 시스템을 뒷받침하는 핵심 개념에 대한 기초적인 이해를 확립해 보겠습니다.
반응형 상태(Reactive State): 변경될 때 사용자 인터페이스의 잠재적인 업데이트를 트리거해야 하는 모든 데이터를 의미합니다. Vue에서는 특정 데이터를 반응형으로 명시적으로 선언합니다.
의존성 추적(Dependency Tracking): 코드의 어떤 부분(예: 템플릿 렌더링, 계산된 속성)이 특정 반응형 상태 조각에 의존하는지 '알고' 있는 시스템의 기능입니다.
변경 감지(Change Detection): 반응형 상태의 수정을 모니터링하는 메커니즘입니다.
부작용(Side Effect) 또는 효과(Effect): 반응형 상태를 읽고 해당 상태 변경 시 다시 실행되어야 할 수 있는 모든 작업입니다. 가장 일반적인 효과는 컴포넌트 템플릿을 렌더링하는 것입니다.
이러한 개념을 염두에 두고 ref
와 reactive
가 반응형 상태를 어떻게 생성하고 effect
가 업데이트를 어떻게 조정하는지 살펴보겠습니다.
반응형 상태 생성
Vue 3는 ref
와 reactive
의 두 가지 주요 방법으로 반응형 상태를 선언할 수 있도록 합니다. 선택은 종종 처리하는 데이터 유형에 따라 달라집니다.
기본값에 대한 ref
ref
는 주로 숫자, 문자열, 부울과 같은 기본값 또는 객체에 대한 반응형 참조를 만드는 데 사용됩니다. .value
속성을 가진 객체로 값을 래핑합니다. 이 래퍼를 통해 반응성 시스템은 내부 기본값의 변경을 추적할 수 있습니다.
import { ref } from 'vue'; // 반응형 숫자 생성 const count = ref(0); console.log(count.value); // 0 // 반응형 숫자 수정 count.value++; console.log(count.value); // 1 // Vue 컴포넌트에서의 예시 // <template> // <p>Count: {{ count }}</p> // <button @click="count++">Increment</button> // </template> // <script setup> // import { ref } from 'vue'; // const count = ref(0); // </script>
템플릿 또는 effect
에서 count.value
에 액세스하면 Vue의 반응성 시스템이 의존성을 "등록"합니다. 나중에 count.value
가 수정되면 시스템은 등록된 모든 의존성에 "알림"을 보내 다시 실행을 트리거합니다.
객체 및 배열에 대한 reactive
reactive
는 반응형 객체 및 배열을 만드는 데 사용됩니다. 일반 JavaScript 객체를 반응형 프록시로 변환합니다. 이 객체 내의 모든 중첩된 속성 또는 배열 내의 요소도 반응형이 됩니다.
import { reactive } from 'vue'; // 반응형 객체 생성 const user = reactive({ name: 'Alice', age: 30, address: { street: '123 Main St', city: 'Anytown' } }); console.log(user.name); // Alice // 속성 수정 user.age++; console.log(user.age); // 31 // 중첩된 속성 수정 user.address.city = 'Newcity'; console.log(user.address.city); // Newcity // Vue 컴포넌트에서의 예시 // <template> // <p>Name: {{ user.name }}</p> // <p>City: {{ user.address.city }}</p> // <button @click="user.age++">Grow Older</button> // </template> // <script setup> // import { reactive } from 'vue'; // const user = reactive({ // name: 'Bob', // age: 25, // address: { city: 'Oldcity' } // }); // </script>
reactive
는 내부적으로 JavaScript의 Proxy
객체를 사용합니다. 반응형 객체의 속성에 액세스하거나 수정하면 Proxy
가 이러한 작업을 가로챕니다. 이를 통해 Vue는 의존성 추적(속성이 읽힐 때)을 수행하고 업데이트 트리거(속성이 쓰여질 때)를 할 수 있습니다.
ref
와 reactive
의 관계
setup
함수 내에서 독립적인 반응형 상태로 의도된 객체에 대해 ref
를 사용하는 것을 흔히 볼 수 있습니다. ref
가 객체를 보유할 때 Vue는 내부적으로 reactive
를 사용하여 해당 객체를 자동으로 반응형으로 만듭니다. 따라서 ref({ data: 'value' })
는 기능적으로 reactive({ data: 'value' })
와 유사하지만, 전자는 여전히 .value
액세스가 필요합니다.
reactive
는 반응형 객체를 직접 노출하는 반면, ref
는 기본값과 객체 모두에 대해 일관된 인터페이스(.value
)를 제공하여 특히 반응형 값을 전달할 때 일부 패턴을 단순화할 수 있습니다.
오케스트레이터 effect
반응성이 실제로 "무언가를" 하는 방식의 핵심에는 effect
함수가 있습니다. 일반적으로 애플리케이션 개발용으로 직접 노출되지는 않지만(라이브러리를 구축하거나 반응성에 깊이 들어가지 않는 한), 기본 메커니즘을 이해하는 데 중요합니다. effect
는 함수를 인수로 받으며, 이 함수는 즉시 실행되고 해당 함수 내에서 액세스되는 모든 반응형 의존성이 변경될 때마다 다시 실행됩니다.
Vue의 렌더링 메커니즘(템플릿 컴파일)은 암묵적으로 effect
를 생성합니다. 컴포넌트의 <template>
을 정의할 때 Vue는 이를 렌더 함수로 컴파일합니다. 이 렌더 함수는 effect
가 됩니다. 템플릿에서 count.value
가 읽힐 때 렌더 함수는 count
를 의존성으로 등록합니다. count.value
가 변경되면 effect
(렌더 함수)가 다시 실행되어 컴포넌트가 다시 렌더링됩니다.
effect
가 이를 어떻게 촉진하는지 간단화된 버전을 보여드리겠습니다.
// 이것은 직접 실행 가능한 Vue 코드가 아닌 개념적인 표현입니다. // 단순화된 반응성 코어라고 가정하십시오. let activeEffect = null; const targetMap = new WeakMap(); // 대상 객체를 prop과 effect의 Set에 대한 Map으로 매핑 function track(target, key) { if (activeEffect) { let depsMap = targetMap.get(target); if (!depsMap) { targetMap.set(target, (depsMap = new Map())); } let dep = depsMap.get(key); if (!dep) { depsMap.set(key, (dep = new Set())); } dep.add(activeEffect); } } function trigger(target, key) { const depsMap = targetMap.get(target); if (!depsMap) return; const dep = depsMap.get(key); if (dep) { dep.forEach(effect => effect()); } } // 단순화된 ref 구현 function ref(raw) { const r = { get value() { track(r, 'value'); return raw; }, set value(newVal) { raw = newVal; trigger(r, 'value'); } }; return r; } // 단순화된 reactive (Proxy) 구현 function reactive(obj) { return new Proxy(obj, { get(target, key, receiver) { track(target, key); return Reflect.get(target, key, receiver); }, set(target, key, value, receiver) { const result = Reflect.set(target, key, value, receiver); trigger(target, key); return result; } }); } function effect(fn) { const effectFn = () => { activeEffect = effectFn; // 현재 활성 효과 설정 try { return fn(); // 함수 실행, 트랙 트리거 } finally { activeEffect = null; // 활성 효과 지우기 } }; effectFn(); // 즉시 실행 return effectFn; // 수동 중지용 반환 (필요한 경우) } // --- 사용 예시 --- const myCount = ref(0); const myUser = reactive({ name: 'Bob' }); effect(() => { console.log(`Count changed: ${myCount.value}`); }); effect(() => { console.log(`User name changed: ${myUser.name}`); }); myCount.value++; // "Count changed: 1" 트리거 myUser.name = 'Alice'; // "User name changed: Alice" 트리거
이 단순화된 모델에서:
effect
가 호출되면activeEffect
가 현재 함수로 설정됩니다.- 함수 내에서
myCount.value
에 액세스하면 해당get
핸들러가track
을 호출합니다. track
은activeEffect
를 사용하여 현재effect
함수를myCount
(특히value
속성)의 의존성으로 등록합니다.- 마찬가지로
myUser.name
에 액세스하여get
핸들러(Proxy
에서)가track
을 호출하면effect
를myUser
(특히name
속성)의 의존성으로 등록합니다. myCount.value
가 업데이트되면 해당set
핸들러가trigger
를 호출합니다.trigger
는myCount
의value
속성에 등록된 모든 효과를 찾아서 다시 실행합니다.myUser.name
이 업데이트될 때도 동일한 논리가 적용됩니다.
이러한 ref
및 reactive
프록시 내의 track
과 trigger
의 지속적인 상호 작용은 effect
실행자에 의해 조정되며, Vue의 매우 효율적이고 자동화된 반응성 시스템의 기반을 형성합니다.
결론
Vue 3의 반응성 시스템은 ref
, reactive
및 기본 effect
메커니즘을 기반으로 구축되어 애플리케이션 상태를 관리하고 UI 일관성을 보장하는 강력하고 직관적인 방법을 제공합니다. 기본값에는 ref
를 사용하고 객체에는 reactive
를 사용하여 개발자는 반응형 상태를 선언할 수 있으며, 이는 컴포넌트 렌더링을 포함한 모든 종속 효과를 자동으로 트리거합니다. 이 선언적 접근 방식은 복잡한 상태 관리를 크게 단순화하여 Vue 애플리케이션을 더 예측 가능하고 유지 관리하기 쉽게 만듭니다. 이 시스템의 우아함은 의존성을 자동으로 추적하고 업데이트를 정확하게 국소화하는 능력에 있으며, 이는 매우 최적화되고 성능이 뛰어난 사용자 인터페이스로 이어집니다.