FrameWorks/Vue

reactive( )

ABCD 2023. 7. 11.

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( ) 구현>

Vue3의 반응성 완벽 이해 1편 : 종속성 추적
vue mastery의 리액트 코어팀이 설명해주는 vue3의 반응성의 원리를 알아봅니다. 공식 문서와 nhn의 게시물을 읽어봤지만, 약간 납득이 안되는 부분들이 있었습니다. 해당 강의를 통해 어느정도 해소된 느낌입니다. 해당 아티클에서 다룰 내용 Vue3 Reactive 모듈의 디자인 패턴 및 디자인 디시전 Vue3 반응성의 원리 뷰의 반응성 이해하기 아래의 간단한 뷰 앱을 봅시다. 해당 앱의 뷰모델은 세 가지 역할을 수행해야 합니다. 페이지의 product.price 값 업데이트 페이지의 product.price * product.quantity 값 업데이트 totalPriceWithTax 함수를 다시 호출하고 페이지 업데이트 Price: ${{ product.price }} Total: ${{ pro..
https://itchallenger.tistory.com/m/815

<JavaScript의 Proxy와 Reflect>

📚 자바스크립트 Proxy & Reflect 고급 기법
JavaScript Proxy 객체 프록시(Proxy)의 사전적 뜻은 '대리인', '대리'라는 뜻이다. 서버를 다뤄본 독자분들이라면 프록시 서버에 대해 질리도록 들어봤을 것이다. 프록시 서버는 클라이언트와 본 서버 중간에 위치하여 캐싱, 분산 등 여러 부가 기능을 수행하는 대리자 역할로서 정말 유용하게 다뤄지는 개념이다. 심지어 디자인 패턴에서는 따로 프록시 패턴(Proxy Pattern) 으로 코드 패턴을 정의하여 소개하기도 한다. 이 프록시 코드 패턴은 실무에서 정말 빈번하게 다뤄지기 때문에, 이에 각각 프로그래밍 언어에서는 별도의 프록시 객체를 만들어서 아예 개발자에게 API로 제공해준다. 대표적으로 자바(Java) 진영의 Dynamic Proxy 를 들 수 있으며, 자바스크립트(JavaScript..
https://inpa.tistory.com/entry/JS-📚-자바스크립트-Proxy-Reflect-고급-기법
Vue3의 반응성 완벽 이해 1편 : 종속성 추적
vue mastery의 리액트 코어팀이 설명해주는 vue3의 반응성의 원리를 알아봅니다. 공식 문서와 nhn의 게시물을 읽어봤지만, 약간 납득이 안되는 부분들이 있었습니다. 해당 강의를 통해 어느정도 해소된 느낌입니다. 해당 아티클에서 다룰 내용 Vue3 Reactive 모듈의 디자인 패턴 및 디자인 디시전 Vue3 반응성의 원리 뷰의 반응성 이해하기 아래의 간단한 뷰 앱을 봅시다. 해당 앱의 뷰모델은 세 가지 역할을 수행해야 합니다. 페이지의 product.price 값 업데이트 페이지의 product.price * product.quantity 값 업데이트 totalPriceWithTax 함수를 다시 호출하고 페이지 업데이트 Price: ${{ product.price }} Total: ${{ pro..
https://itchallenger.tistory.com/m/815

Uploaded by N2T

728x90
반응형

'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

댓글

💲 추천 글