[Front-end] - Vue data reactivity

November 3, 2022

Created: July 6, 2022 2:15 PM Date: July 6, 2022 2:15 PM Tags: Vue.js, observer, proxy

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.

Untitled

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()
  },
})

์œ„ ํ•จ์ˆ˜๋Š” ์ดˆ๊ธฐํ™” defineReactive ๋  ๋•Œ ๋ฐ์ดํ„ฐ ๊ฐœ์ฒด์˜ ๋ชจ๋“  ์†์„ฑ์— ๋Œ€ํ•ด ํ˜ธ์ถœ๋œ๋‹ค.

Observer getter๊ฐ€ ์ข…์†์„ฑ์„ ์ˆ˜์ง‘ํ•˜๋„๋ก ์ •์˜๋œ ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์œผ๋ฉฐ ์„ค์ •์€ ๋ฐ์ดํ„ฐ ๋ณ€๊ฒฝ์„ ๋ชจ๋‹ˆํ„ฐ๋งํ•˜๊ณ  ๋ณ€๊ฒฝ์ด ๊ฐ์ง€๋˜๋ฉด ์•Œ๋ฆผ์„ ๋ณด๋‚ธ๋‹ค.

์œ„ ๋ฉ”์ปค๋‹ˆ์ฆ˜์—์„œ ๋‘๊ฐ€์ง€ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•จ

  • ์†์„ฑ์˜ ์‚ญ์ œ or ์ถ”๊ฐ€๋ฅผ ๊ฐ์ง€ํ•  ์ˆ˜ ์—†์Œ
    • ๋ฐ˜์‘์„ฑ์€ ์•ฑ์ด ์ดˆ๊ธฐํ™”๋  ๋•Œ๋งŒ ์ ์šฉ๋œ๋‹ค. ๋Ÿฐํƒ€์ž„์— ์ƒˆ ์†์„ฑ์„ ์ถ”๊ฐ€ํ•˜๋ฉด ์ƒˆ ์†์„ฑ์€ ๋ฐ˜์‘ํ•˜์ง€ ์•Š๋Š”๋‹ค. ์ฆ‰, ์†์„ฑ ๊ฐ’์„ ๋ณ€๊ฒฝํ•ด๋„ ๋ฐ˜์‘์ ์ธ ๋ถ€์ž‘์šฉ์ด ๋ฐœ์ƒํ•˜์ง€ ์•Š๋Š”๋‹ค. Vue2๋Š” .set ์„ ๊ฐœ๋ฐœ์ž๊ฐ€ ์ˆ˜๋™์œผ๋กœ ์†์„ฑ์„ ๋ฐ˜์‘ํ˜•์œผ๋กœ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜๋Š” ํ•ด๊ฒฐ๋ฐฉ๋ฒ•์„ ์ œ๊ณตํ•œ๋‹ค.
  • ์„ฑ๋Šฅ
    • ๋Œ€๊ทœ๋ชจ/ ์ค‘์ฒฉ ๋ฐ์ดํ„ฐ ์…‹์˜ ๊ฒฝ์šฐ vue2๊ฐ€ ๋ชจ๋“  ์†์„ฑ์˜ ๋ฐ์ดํ„ฐ ํšก๋‹จ์— getter/setter ์ƒ์„ฑ์ด ํ•„์š”ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์„ฑ๋Šฅ์— ๋ถ€์ •์ ์ธ ์˜ํ–ฅ์„ ๋ฏธ์น  ์ˆ˜ ์žˆ๋‹ค.

vue3

es6์— ๋„์ž…๋œ Proxy๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ธฐ์กด์˜ ์ƒํƒœ๊ฐ์ฒด์— getter/setter๋ฅผ ์ด๊ฒƒ์œผ๋กœ ๋Œ€์ฒดํ•˜์—ฌ ๋ฐ˜์‘์„ฑ์„ ๊ตฌํ˜„๊ฐ€๋Šฅ.

ํ”„๋ก์‹œ๋Š” ์ƒˆ๋กœ์šด ์†์„ฑ ์ถ”๊ฐ€๋ฅผ ๊ฐ์ง€ํ•  ์ˆ˜ ์žˆ์Œ

MDN Proxy

[Proxy - JavaScript MDN](https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/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
}
  • ์œ„ ์ฝ”๋“œ๋Š” ๊ฐ์ฒด๋งŒ ์ฒ˜๋ฆฌํ•˜๊ธฐ๋ฅผ ์›ํ•˜๋ฏ€๋กœ ๊ธฐ๋ณธ ์œ ํ˜•์˜ ๋ฐ์ดํ„ฐ๊ฐ€ ์ง์ ‘ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. 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)
  }
}

์†์„ฑ์ด ์กด์žฌํ•˜์ง€ ์•Š๋Š” ๊ฒฝ์šฐ(hadKey false์ธ ๊ฒฝ์šฐ) ํŠธ๋ฆฌ๊ฑฐ ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ƒˆ์†์„ฑ์ด ์ถ”๊ฐ€๋˜์—ˆ์Œ์„ ์ข…์†์„ฑ์— ์•Œ๋ฆฐ๋‹ค. ์ด๊ฒƒ์€ Vue2์—์„œ ์ถ”๊ฐ€ ์†์„ฑ์„ ๊ฐ์ง€ํ•  ์ˆ˜ ์—†๋Š” ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•œ๋‹ค. ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ deleteProperty ํ•ธ๋“ค๋Ÿฌ ์ž‘์—…์€ Vue2์—์„œ ์‚ญ์ œ ์†์„ฑ์„ ๊ฐ์ง€ ํ•  ์ˆ˜ ์—†๋Š” ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•œ๋‹ค.

Vue3์—์„œ ๋ฐ˜์‘์„ฑ API์˜ ํ”„๋ก์‹œ ๊ตฌํ˜„์„ ์‚ฌ์šฉํ•˜๋ฉด ๋ฐ์ดํ„ฐ์˜ ๋ชจ๋“  ์†์„ฑ์„ ํƒ์ƒ‰ํ•  ํ•„์š”๊ฐ€ ์—†๋‹ค. ์ด๋Š” ๋Œ€๊ทœ๋ชจ ๋ฐ์ดํ„ฐ ์„ธํŠธ๋ฅผ ์ฒ˜๋ฆฌํ•  ๋•Œ ์ƒ๋‹นํ•œ ์„ฑ๋Šฅ ํ–ฅ์ƒ์„ ๊ฐ€์ ธ์˜จ๋‹ค.

๊ฒฐ๋ก 

ํ”„๋ก์‹œ๋Š” ๊ฐ•๋ ฅํ•œ ๋ฉ”ํƒ€ํ”„๋กœ๊ทธ๋ž˜๋ฐ ๊ธฐ๋Šฅ์ด๋‹ค. Vue3์—์„œ Proxy๋ฅผ ์ ์šฉํ•˜๋Š” ๊ฒƒ์€ ํ›Œ๋ฅญํ•œ ์†”๋ฃจ์…˜์ž…๋‹ˆ๋‹ค. Vue2์˜ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•  ๋ฟ ์•„๋‹ˆ๋ผ ์ถ”๊ฐ€ ํ™•์žฅ ๊ฐ€๋Šฅ์„ฑ๋„ ์—ด์–ด์ค€๋‹ค.


์ฐธ๊ณ 

When Vue Meets Proxy

results matching ""

    No results matching ""