Vue data reactivity

Updated
2024/09/11 15:22
Category
Front-end
Tags
Vue.js
2 more properties

Vue 2 data reactivity

In Vue 2, data reactivity is achieved by traversing the data, and making use of Object.definedProperty() to convert its properties to getter/setter. It collects data dependencies via custom getter, and monitors data change and subscribe events in a custom setter.

defineReactive 코드분석

Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter () { const value = getter ? getter.call(obj) : val if (Dep.target) { dep.depend() if (childOb) { childOb.dep.depend() if (Array.isArray(value)) { dependArray(value) } } } return value }, set: function reactiveSetter (newVal) { const value = getter ? getter.call(obj) : val /* eslint-disable no-self-compare */ if (newVal === value || (newVal !== newVal && value !== value)) { return } /* eslint-enable no-self-compare */ if (process.env.NODE_ENV !== 'production' && customSetter) { customSetter() } // #7981: for accessor properties without setter if (getter && !setter) return if (setter) { setter.call(obj, newVal) } else { val = newVal } childOb = !shallow && observe(newVal) dep.notify() } })
JavaScript
복사
위 함수는 초기화 defineReactive 될 때 데이터 개체의 모든 속성에 대해 호출된다.
Observer getter가 종속성을 수집하도록 정의된 것을 볼 수 있으며 설정은 데이터 변경을 모니터링하고 변경이 감지되면 알림을 보낸다.

위 메커니즘에서 두가지 문제가 발생함

속성의 삭제 or 추가를 감지할 수 없음
반응성은 앱이 초기화될 때만 적용된다. 런타임에 새 속성을 추가하면 새 속성은 반응하지 않는다. 즉, 속성 값을 변경해도 반응적인 부작용이 발생하지 않는다. Vue2는 .set 을 개발자가 수동으로 속성을 반응형으로 추가할 수 있도록 하는 해결방법을 제공한다.
성능
대규모/ 중첩 데이터 셋의 경우 vue2가 모든 속성의 데이터 횡단에 getter/setter 생성이 필요하기 때문에 성능에 부정적인 영향을 미칠 수 있다.

vue3

es6에 도입된 Proxy를 사용하여 기존의 상태객체에 getter/setter를 이것으로 대체하여 반응성을 구현가능.
프록시는 새로운 속성 추가를 감지할 수 있음

MDN Proxy

createReactiveObject 분석

function createReactiveObject( target: Target, isReadonly: boolean, baseHandlers: ProxyHandler<any>, collectionHandlers: ProxyHandler<any>, proxyMap: WeakMap<Target, any> ) { if (!isObject(target)) { if (__DEV__) { console.warn(`value cannot be made reactive: ${String(target)}`) } return target } // target is already a Proxy, return it. // exception: calling readonly() on a reactive object if ( target[ReactiveFlags.RAW] && !(isReadonly && target[ReactiveFlags.IS_REACTIVE]) ) { return target } // target already has corresponding Proxy const existingProxy = proxyMap.get(target) if (existingProxy) { return existingProxy } // only a whitelist of value types can be observed. const targetType = getTargetType(target) if (targetType === TargetType.INVALID) { return target } const proxy = new Proxy( target, targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers ) proxyMap.set(target, proxy) return proxy }
JavaScript
복사
위 코드는 객체만 처리하기를 원하므로 기본 유형의 데이터가 직접 반환합니다. vue3는 대신 ref를 사용하여 기본 유형을 처리. 내부적으로 반응 프록시 개체를 사용
객체에 이미 해당 프록시가 있을 경우 캐시된 프록시를 직접 반환
대상 객체 유형을 유추하면 vue3는 Array, Object, Map, Set, WeakMap, WeakSet에 대해서만 프록시를 생성. 대상 유형 외부의 개체는 INVALID로 설정되고 반환
새 프록시 개체를 만들고 반환하기 전에 proxyMap캐시에 저장

Proxy는 Vue2 반응성 문제를 어떻게 해결할까?

proxy 개체를 사용하면 개체에 액세스하거나 개체를 수정하기 위한 모든 호출이 차단된다. 사용자 정의 작업은 getter / setter에서 정의된다.
const hadKey = isArray(target) && isIntegerKey(key) ? Number(key) < target.length : hasOwn(target, key) const result = Reflect.set(target, key, value, receiver) // don't trigger if target is something up in the prototype chain of original if (target === toRaw(receiver)) { if (!hadKey) { trigger(target, TriggerOpTypes.ADD, key, value) } else if (hasChanged(value, oldValue)) { trigger(target, TriggerOpTypes.SET, key, value, oldValue) } }
JavaScript
복사
속성이 존재하지 않는 경우(hadKey false인 경우) 트리거 메서드를 사용하여 새속성이 추가되었음을 종속성에 알린다. 이것은 Vue2에서 추가 속성을 감지할 수 없는 문제를 해결한다. 마찬가지로 deleteProperty 핸들러 작업은 Vue2에서 삭제 속성을 감지 할 수 없는 문제를 해결한다.
Vue3에서 반응성 API의 프록시 구현을 사용하면 데이터의 모든 속성을 탐색할 필요가 없다. 이는 대규모 데이터 세트를 처리할 때 상당한 성능 향상을 가져온다.

결론

프록시는 강력한 메타프로그래밍 기능이다. Vue3에서 Proxy를 적용하는 것은 훌륭한 솔루션입니다. Vue2의 문제를 해결할 뿐 아니라 추가 확장 가능성도 열어준다.

참고