Vue2 is updated to vue3 in response to data, with significant adjustments.

So let’s review the response formula for VUe2 what did we do

Object defineProperty()

Vue2 array responsive principle: override can modify the array of 7 methods, from the array prototype to obtain these 7 methods, and override can send update notification function implementation

Object property hijacking

// Specify the specified key interceptor
function defineReactive(obj, key, val) {
  // recursive traversal
  observe(val)

  // Val is actually a closure
  Object.defineProperty(obj, key, {
    get() {
      return val
    },
    set(newVal) {
      if(newVal ! == val) {// Val can be an object
        observe(newVal)
        notifyUpdate()
        val = newVal
      }
    }
  })
}
Copy the code

Hijacking of array properties

// Modify the array's 7 API prototypes
const originalProto = Array.prototype
const arrayProto = Object.create(originalProto) ; ['push'.'pop'.'shift'.'unshift'.'splice'.'reverse'.'sort'].forEach(
  method= > {
    arrayProto[method] = function() {
      // Do what you did before
      originalProto[method].apply(this.arguments)

      // Notification update
      notifyUpdate()
    }
  }
)
Copy the code

Data response

// Idea: recursively traverse the incoming OBj, defining interceptions for each attribute
function observe(obj) {
  if (typeofobj ! = ='object' || obj == null) {
    return obj
  }

  // Determine the type: if it is an array, replace its prototype
  if (Array.isArray(obj)) {
    Object.setPrototypeOf(obj, arrayProto)
  } else {
    const keys = Object.keys(obj)
    for (let index = 0; index < keys.length; index++) {
      const key = keys[index]
      // Intercepts obj for each key
      defineReactive(obj, key, obj[key])
    }
  }
}
Copy the code

With a New Deal with

function notifyUpdate() {
  console.log('Page updated! ')}const data = { foo: 'foo'.bar: { a: 1 }, tua: [1.2.3] }
observe(data)
// 1. Common updates
// data.foo = 'foooooooo'
// 2. Nested attribute updates
// data.bar.a = 10
// data.dong = 'lalala' // no ok
// 3. Assignment is an object
// data.bar = {a:10}
4 / / array
// data.tua.push(4)
Copy the code

What’s wrong with this approach?

  1. Large amount of data needs to be regenerated, resulting in poor performance and high consumption of recursive traversal
  2. New or deleted properties cannot be listened on
  3. Array responsivity requires additional implementation
  4. There are limitations to changing the syntax

Therefore, we made a more optimized plan in VUE3

The realization of vue3’s responsivity principle

Vue3 response principle: UseProxyObject intercepts data

// WeakMap caches proxy data and raw data in a weak-reference way
const toProxy = new WeakMap(a)// obj: observed
const toRaw = new WeakMap(a)// Shape like observed: obj

function isObject(obj) {
  return typeof obj === 'object' || obj === null
}

function hasOwn(obj, key) {
  return obj.hasOwnProperty(key)
}
// Response object data
function reactive(obj) {
  if(! isObject(obj)) {return obj
  }

  // Find the cache
  if (toProxy.has(obj)) {
    return toProxy.get(obj)
  }
  // Obj is a proxy object
  if (toRaw.has(obj)) {
    return obj
  }

  const observed = new Proxy(obj, {
    get(target, key, receiver) {
      / / access
      const res = Reflect.get(target, key, receiver)
      console.log(` access${key}: ${res}`)

      // Rely on collection
      track(target, key)

      return isObject(res) ? reactive(res) : res
    },
    set(target, key, value, receiver) {
      // Add and update
      const hadKey = hasOwn(target, key) / / ADD or SET
      const oldVal = target[key]
      const res = Reflect.set(target, key, value, receiver)
      if(! hadKey) {console.log(` new${key}:${value}`)
        trigger(target, 'ADD', key)
      } else if(oldVal ! == value) {console.log(` set${key}:${value}`)
        trigger(target, 'SET', key)
      }
      return res
    },
    deleteProperty(target, key) {
      / / delete
      const hadKey = hasOwn(target, key)
      const res = Reflect.deleteProperty(target, key)
      // The key exists and was deleted successfully
      if (res && hadKey) {
        console.log(` delete${key}:${res}`)
        trigger(target, 'DELETE', key)
      }
      return res
    }
  })
  
  / / cache
  toProxy.set(obj, observed)
  toRaw.set(observed, obj)

  return observed
}
// The response callback method for each attribute
const activeReativeEffectStack = []

// Rely on collection execution
{target:{key:[eff1, eff2]}}
let targetsMap = new WeakMap(a)function track(target, key) {
  // Get the response function from the stack
  const effect = activeReativeEffectStack[activeReativeEffectStack.length - 1]
  if (effect) {
    let depsMap = targetsMap.get(target)
    if(! depsMap) {// Access target for the first time
      depsMap = new Map()
      targetsMap.set(target, depsMap)
    }

    / / store the key
    let deps = depsMap.get(key)
    if(! deps) { deps =new Set()
      depsMap.set(key, deps)
    }
    if(! deps.has(effect)) { deps.add(effect) } } }// Data changes respond to callbacks
function effect(fn) {
  // 1. Exception handling
  // 2. Execute function
  // 3. Place it on the Active EffectStack
  const rxEffect = function(. args) {
    try {
      activeReativeEffectStack.push(rxEffect)
      returnfn(... args)// Execute the function to trigger dependency collection
    } finally {
      activeReativeEffectStack.pop()
    }  
  }

  rxEffect() // Execute immediately by default
  return rxEffect
}


// Trigger the target.key response function
function trigger(target, type, key) {
  // Get the dependency table
  const depsMap = targetsMap.get(target)
  if (depsMap) {
    // Get the set of response functions
    const deps = depsMap.get(key)
    const effects = new Set(a)if (deps) {
      // Execute all response functions
      deps.forEach(effect= > {
        // effect()
        effects.add(effect)
      })
    }

    // Array added or deleted
    if (type === 'ADD' || type === 'DELETE') {
      if (Array.isArray(target)) {
        const deps = depsMap.get('length')
        if (deps) {
          deps.forEach(effect= > {
            effects.add(effect)
          })
        }
      }
    }
    // Get the existing Dep Set execution
    effects.forEach(effect= > effect())
  }
}


const data = { foo: 'foo'.bar: { a: 1}}const react = reactive(data)
/ / 1. Access
// react.foo // ok
// 2. Set existing properties
// react.foo = 'foooooooo'
// 3. Set nonexistent properties
// react.baz = 'bazzzzzz'
// 4. Nested objects
// react.bar.a = 10

// Avoid duplicate proxies
// console.log(reactive(data) === react) // true
// reactive(react)
effect(() = > {
  console.log('count has changed: ', react.foo)
  // dom
})
react.foo = 'fooooooo'
Copy the code

Vue responsive data update process