Vue reactive function
const targetMap = new WeakMap() // targetMap stores the effects that each object should re-run when it's updated
let activeEffect = null // The active effect running
function track(target, key) {
if (activeEffect) {
// <------ Check to see if we have an activeEffect
// We need to make sure this effect is being tracked.
let depsMap = targetMap.get(target) // Get the current depsMap for this target
if (!depsMap) {
// There is no map.
targetMap.set(target, (depsMap = new Map())) // Create one
}
let dep = depsMap.get(key) // Get the current dependencies (effects) that need to be run when this is set
if (!dep) {
// There is no dependencies (effects)
depsMap.set(key, (dep = new Set())) // Create a new Set
}
dep.add(activeEffect) // Add effect to dependency map
}
}
function trigger(target, key) {
const depsMap = targetMap.get(target) // Does this object have any properties that have dependencies (effects)
if (!depsMap) {
return
}
let dep = depsMap.get(key) // If there are dependencies (effects) associated with this
if (dep) {
dep.forEach((effect) => {
// run them all
effect()
})
}
}
function reactive(target) {
const handler = {
get(target, key, receiver) {
let result = Reflect.get(target, key, receiver)
track(target, key) // If this reactive property (target) is GET inside then track the effect to rerun on SET
return result
},
set(target, key, value, receiver) {
let oldValue = target[key]
let result = Reflect.set(target, key, value, receiver)
if (result && oldValue != value) {
trigger(target, key) // If this reactive property (target) has effects to rerun on SET, trigger them.
}
return result
},
}
return new Proxy(target, handler)
}
function effect(eff) {
activeEffect = eff
activeEffect()
activeEffect = null
}
let product = reactive({ price: 5, quantity: 2 })
let salePrice = 0
let total = 0
effect(() => {
total = product.price * product.quantity
})
effect(() => {
salePrice = product.price * 0.9
})
console.log(
`Before updated quantity total (should be 10) = ${total} salePrice (should be 4.5) = ${salePrice}`
)
product.quantity = 3
console.log(
`After updated quantity total (should be 15) = ${total} salePrice (should be 4.5) = ${salePrice}`
)
product.price = 10
console.log(
`After updated price total (should be 30) = ${total} salePrice (should be 9) = ${salePrice}`
)
직접 호출하는 방법
- WeakMap - 객체를 key로 사용하는 JavaScript Map
const targetMap = new WeakMap() // targetMap stores the effects that each object should re-run when it's updated
function track(target, key) {
// We need to make sure this effect is being tracked.
let depsMap = targetMap.get(target) // Get the current depsMap for this target
if (!depsMap) {
// There is no map.
targetMap.set(target, (depsMap = new Map())) // Create one
}
let dep = depsMap.get(key) // Get the current dependencies (effects) that need to be run when this is set
if (!dep) {
// There is no dependencies (effects)
depsMap.set(key, (dep = new Set())) // Create a new Set
}
dep.add(effect) // Add effect to dependency map
}
function trigger(target, key) {
const depsMap = targetMap.get(target) // Does this object have any properties that have dependencies (effects)
if (!depsMap) {
return
}
let dep = depsMap.get(key) // If there are dependencies (effects) associated with this
if (dep) {
dep.forEach(effect => {
// run them all
effect()
})
}
}
let product = { price: 5, quantity: 2 }
let total = 0
let effect = () => {
total = product.price * product.quantity
}
// 객체를 키로, 객체의 quantitiy 구독
track(product, 'quantity')
effect()
console.log(total) // --> 10
product.quantity = 3
trigger(product, 'quantity')
console.log(total) // --> 15
Proxy and Reflect
반응성 객체에서 get 및 set 메서드를 hooking(또는 리스닝)하는 방법이 필요합니다.
- GET 속성 => 현재 효과를 추적해야 합니다.
- SET 속성 => 이 속성에 대해 추적된 종속성(effect)을 트리거해야 합니다.
이를 수행하는 방법을 이해하는 첫 번째 단계는
Vue 3에서 ES6 Reflect 및 Proxy를 사용하여 GET 및 SET 호출을 가로채는 방법을 이해하는 것입니다.
Vue 2에서는 ES5 Object.defineProperty로 이 작업을 수행했습니다.
이는 기존 객체를 직접 조작하며, 프록시와 다르게 반응형을 레이지하게 구현하기 어렵다는 단점이 있었습니다.
Understanding ES6 Reflect
객체 속성을 다음과 같이 print 할 수 있습니다.
let product = { price: 5, quantity: 2 }
console.log('quantity is ' + product.quantity)
// orconsole.log('quantity is ' + product['quantity'])
그러나 Reflect를 사용하여 객체의 값을 GET할 수도 있습니다.
Reflect를 사용하면 객체의 속성을 가져올 수 있습니다.
위에서 쓴 것을 수행하는 또 다른 방법입니다.
console.log('quantity is ' + Reflect.get(product, 'quantity'))
Reflect를 사용하는 이유는 뭘까요?
Understanding ES6 Proxy
프록시는 기본적으로 메서드의 실행을 대상 객체에 위임하는 Placeholder입니다.
따라서 다음 코드를 실행하면 quantity의 get을 다른 객체에 위임합니다.
let product = { price: 5, quantity: 2 }
let proxiedProduct = new Proxy(product, {})
console.log(proxiedProduct.quantity)
프록시 객체의 {}를 사용하는 두 번째 인수에 주목하세요
이것을 핸들러라고 하며 get 및 set 호출을 가로채는 것과 같이
프록시 객체의 동작을 사용자 정의할 수 있습니다.
이러한 인터셉터 메서드를 트랩(trap)이라고 합니다.
프록시 생성자의 두번째 인자 핸들러 객체의 메서드 : 트랩
핸들러에서 get 트랩을 설정하는 방법은 다음과 같습니다.
let product = { price: 5, quantity: 2 }
let proxiedProduct = new Proxy(product, {
get() {
console.log('Get was called')
return 'Not the value'
}
})
console.log(proxiedProduct.quantity)
콘솔은 다음과 같이 출력됩니다.
Get was called
Not the value
즉 우리는 대상 객체의 get이 리턴하는 값을 재정의 했습니다.
하지만 우리는 실제 객체의 값을 리턴해야 합니다.
let product = { price: 5, quantity: 2 }
let proxiedProduct = new Proxy(product, {
get(target, key) {// <--- The target (our object) and key (the property name)console.log('Get was called with key = ' + key)
return target[key]
}
})
console.log(proxiedProduct.quantity)
get에는 객체(예:product) 대상(target)과 객체에서 조회하고자 하는 속성(key) 두 가지 인자가 있습니다.
로그 출력은 다음과 같습니다.
Get was called with key = quantity
2
여기에서 Reflect를 사용하여 추가 인수(receiver)를 전달할 수 있습니다.
let product = { price: 5, quantity: 2 }
let proxiedProduct = new Proxy(product, {
get(target, key, receiver) {// <--- notice the receiverconsole.log('Get was called with key = ' + key)
return Reflect.get(target, key, receiver)// <----
}
})
get에는 Reflect.get에 인수로 보내는 receiver라는 추가 매개변수가 있습니다.
이렇게 하면 target 객체가 다른 객체에서 값/함수를 상속받았을 때에도 적절한 this 값이 사용됩니다.
이것이 우리가 항상 Proxy 내부에서 Reflect를 사용하는 이유입니다.
그래서 우리가 커스터마이징하는 원래 동작(get)을 유지할 수 있습니다.
이제 setter 메서드를 추가해 보겠습니다. 별 차이는 없습니다.
let product = { price: 5, quantity: 2 }
let proxiedProduct = new Proxy(product, {
get(target, key, receiver) {
console.log('Get was called with key = ' + key)
return Reflect.get(target, key, receiver)
}
set(target, key, value, receiver) {
console.log('Set was called with key = ' + key + ' and value = ' + value)
return Reflect.set(target, key, value, receiver)
}
})
proxiedProduct.quantity = 4
console.log(proxiedProduct.quantity)
set은 대상(product)을 설정하기 위해 값을 받는 Reflect.set을 사용한다는 점을 제외하면 get과 매우 유사합니다.
예상대로 출력은 다음과 같습니다.
Set was called with key = quantity and value = 4
Get was called with key = quantity
4
참고사항
<reactive( ) 구현>

<JavaScript의 Proxy와 Reflect>


Uploaded by N2T
'FrameWorks > Vue' 카테고리의 다른 글
Vue의 watchEffect 와 watch 파헤치기 (0) | 2024.06.12 |
---|---|
Vue의 ModelValue 파헤치기 (0) | 2024.06.12 |
Vue의 nextTick() 파헤치기 (0) | 2024.06.12 |
Vue의 BaseHandler 파헤치기 (0) | 2024.06.12 |
Vue의 ref 파헤치기 (0) | 2024.06.12 |
댓글