preface

As a React fan and heavy reader, vue3. X upgrade points have to be changed a little bit. It’s not an important one, but at least it’s one of them. The Proxy!!!!!!

The Proxy object

  • Set and deleteProperty are required to return Boolean values. In strict mode, a Type Error occurs if false is returned

Reflect is reflect. it is used to get or set a running member during code runtime. In the past, some methods were randomly mounted to objects, such as: Object.getproperty (),Reflect also has a method reflect.getProperty (), which does the same thing. If there is a method in Reflect, it is recommended to use the method in Reflect

'use strict'
const target={
  foo:'xxxx'.bar:'yyyy'
}
const proxy = new Proxy(target,{
  get(target,key,receiver){
    return Reflect.get(... arguments) },set(target,key,receiver){
    return Reflect.get(... arguments) },deleteProperty(target,key){
    return Reflect.get(... arguments) }, }) proxy.foo='zzs'
Copy the code
  • Reaciver used in Proxy and Reflect. In Proxy, reciver is a Proxy object or an object that inherits from Proxy. Reciver in Reflect: If a getter is set in the target object, this in the getter points to Reciver
const obj = {
  get foo() {console.log(this)
    return this.bar
  }
}
const proxy =new Proxy(obg,{
  get(target,key,reciver){
    if(key == 'bar') {return 'value-bar'
    }
    return Reflect.get(target,key)
  }
})
console.log(proxy.foo)
Copy the code

Reactive function

  • Receives a parameter and determines whether the parameter is an object

Reactive only converts objects into reactive objects, which is different from ref

  • Create the interceptor object handler and set get/set/deleteProperty
  • Return Proxy object
const isObject = val= >val ! = =null && typeof val === 'object'
const convert = target= > isObject(tartget) ? reactive(target) : target

/** * determine if an Object itself has a specified property */
const hasOwnProperty= Object.prototype.hasOwnProperty
const hasOwn = (target,key) = >hasOwnProperty.call(target,key)

export function reactive(tartget) {
    // Tartget returns directly if it is not an object
    if(! isObject(tartget))return tartget
    // Otherwise convert target to a proxy object
    // Trap
    const handler = {
        get(target, key, receiver) {
            // Collect dependencies
            console.log('get'.'key')
            /** * return the value of the corresponding key in the target. * If the value of the key is an object, you need to recursively collect the dependencies of the next level in the getter. You also need to convert all the properties in the getter to reactive */
            const result = Reflect.get(target, key, receiver)
            return convert(result)
        },
        set(target, key, value, receiver) {
            const oldValue = Reflect.get(target, key, receiver)
            let result = true
            if(oldValue ! == value) { result =Reflect.set(target, key, value, receiver)
                console.log('set',key,value)
            }
            return result
        },
        deleteProperty(target, key) {
            /** * First determine if the current target has its own key attribute * if it has a key attribute, trigger an update if the key was successfully deleted from the target */
            const hadkey = hasOwn(target,key)
            const result = Reflect.deleteProperty(target, key)
            if(hadkey&& result){
                // Trigger the update
                console.log('delete',key)
            }
            return result
        },
    }
    return new Proxy(tartget, handler)
}
Copy the code

Collect rely on

const product = reactive({
      name: 'iPhone'.price: 5000.count: 3
})
let total = 0 
/** * The first load executes the arrow function in effect. * The arrow function accesses the product, which is a reactive object. That is, when the proxy object * accesses product.price, it accesses the get method of product.price. In the get method, it collects dependencies. The process of collecting dependencies is to store this property and the callback function * property is associated with the object, So the get method of the proxy object will first store the target, then the target attribute price, and then store the corresponding arrow function. So there's a relationship here, the target object, the corresponding property, and the corresponding arrow function. * This attribute is used to find the corresponding function when the update is triggered */
effect(() = > {
  total = product.price * product.count
})
console.log(total)

product.price = 4000
console.log(total)

product.count = 1
console.log(total)
Copy the code

Three collections are created during dependency collection:

  • targetMap

New WeakMap(), targetMap is used to record the target object and a dictionary, that is, the attributes of the target object. The Map used is a weak reference, that is, the stored key is an object. Because it is a weak reference, when the target loses the reference, the target will be destroyed. The value of targetMap is depsMap

  • depsMap

The new Map() is a dictionary, the key is the attribute name of the target object, and the value is a set that stores elements that do not repeat themselves

  • dep

New Set(), which stores the effect function. Because you can call an effect multiple times and access the same property in an effect, the property will collect multiple dependencies for multiple effects

This structure allows you to store the target object, its properties, and the effect function. A property may correspond to more than one function. When an update is triggered in the future, the effect function can be found and executed based on the properties of the target object.

Implementation of effect function

let activeEffect = null
export function effect(callback) {
    // activeEffect records the callback
    activeEffect = callback
    /** * After calling effect, perform a callback * to access the properties of the responsive object to collect dependencies */
    callback()
    // Set this parameter to null after collecting dependencies
    activeEffect = null
}
Copy the code

Implementation of TRACK function


let targetMap = new WeakMap(a)export function track(target, key) {
  /** * Collect the dependent track function by first finding depsMap using the targetMap attribute. * If not, create a depsMap for the current object and add it to the targetMap. * If a dep is found, find the corresponding deP in depsMap based on the current attribute. * dep stores the effect function. If not found, create a map for the current property and store it in depsMap. * If the collection corresponding to the current property is found, the current effect function is stored in the corresponding DEP. * /
    if(! activeEffect)return
    let depsMap = targetMap.get(target)
    if(! depsMap) { targetMap.set(target, (depsMap =new Map()))}let dep = depsMap.get(key)
    if(! dep) { depsMap.set(key, (dep =new Set()))
    }
    dep.add(activeEffect)
}
Copy the code

The trigger to realize

export function trigger (target, key) {
    const depsMap = targetMap.get(target)
    if(! depsMap)return
    const dep = depsMap.get(key)
    if (dep) {
        // Iterate over all of the effec functions and execute them one by one
      dep.forEach(effect= > {
        effect()
      })
    }
  }
Copy the code

ref

export function ref (raw) {
    // Check whether raw is an object created by ref, and return if it is
    if (isObject(raw) && raw.__v_isRef) {
      return
    }
    let value = convert(raw)
    const r = {
        // Create the identity attribute. The identity is the object created by ref
      __v_isRef: true,
      get value () {
        // Call track to collect dependencies
        track(r, 'value')
        return value
      },
      set value (newValue) {
          // Determine whether the old and new values are equal
        if(newValue ! == value) { raw = newValue value = convert(raw) trigger(r,'value')}}}return r
  }
Copy the code

reactive Vs ref

  • Ref can convert basic data types into reactive objects. The. Value attribute is used when fetching data. The value attribute can be omitted from templates. Reactive does not convert basic data types into reactive objects
  • The object returned by ref, when re-assigned to value, is also responsive
  • Reactive reassignment of the returned object loses the response because the reassigned object is no longer a proxy object
  • Reactive objects are not allowed to be structured. If they want to be structured, they are toRefs into reactive objects

toRefs

Reactive object (reactive object) reactive object (reactive object) reactive object (reactive object
  export function toRefs (proxy) {
    const ret = proxy instanceof Array ? new Array(proxy.length) : {}
  
    for (const key in proxy) {
      ret[key] = toProxyRef(proxy, key)
    }
  
    return ret
  }

// Convert each attribute to the object returned by ref
  function toProxyRef (proxy, key) {
    const r = {
      __v_isRef: true,
      get value () {
        return proxy[key]
      },
      set value (newValue) {
        proxy[key] = newValue
      }
    }
    return r
  }
Copy the code

computed

The computed function internally listens for changes in the responsive properties of the getter through effect. This is because when the getter is executed in effect, the accessed properties collect dependencies. When the data changes, the effect function is executed again and the getter results are stored in the result

  export function computed (getter) {
    const result = ref()
  
    effect(() = > (result.value = getter()))
  
    return result
  }
Copy the code