Reactive in deep response

Vue3’s responsiveness is shallow and deep, and Reactive is deep.

As we all know, Reactive uses proxies for responsiveness, so the question is: How does reactive implement deep responsiveness when proxy interception is shallow and insensitive to nested properties?

This has to look at the source code.

// reactivity.js
function createGetter(isReadonly = false, shallow = false) {
    return function get(target, key, receiver) {
        if (key === "__v_isReactive" /* IS_REACTIVE */) {
            return! isReadonly; }else if (key === "__v_isReadonly" /* IS_READONLY */) {
            return isReadonly;
        }
        else if (key === "__v_raw" /* RAW */ &&
            receiver ===
                (isReadonly
                    ? shallow
                        ? shallowReadonlyMap
                        : readonlyMap
                    : shallow
                        ? shallowReactiveMap
                        : reactiveMap).get(target)) {
            return target;
        }
        const targetIsArray = isArray(target);
        if(! isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) {return Reflect.get(arrayInstrumentations, key, receiver);
        }
        const res = Reflect.get(target, key, receiver);
        if (isSymbol(key)
            ? builtInSymbols.has(key)
            : isNonTrackableKeys(key)) {
            return res;
        }
        if(! isReadonly) { track(target,"get" /* GET */, key);
        }
        if (shallow) {
            return res;
        }
        if (isRef(res)) {
            // ref unwrapping - does not apply for Array + integer key.
            constshouldUnwrap = ! targetIsArray || ! isIntegerKey(key);return shouldUnwrap ? res.value : res;
        }
        if (isObject(res)) {
            // Convert returned value into a proxy as well. we do the isObject check
            // here to avoid invalid value warning. Also need to lazy access readonly
            // and reactive here to avoid circular dependency.
            return isReadonly ? readonly(res) : reactive(res);  // The point is...
        }
        return res;
    };
}

Copy the code

This is the code that intercepts the GET operation. We can skip the above and go to the penultimate return.

Simply put, after various judgments, return a new Reactive.

In other words, when assigning attributes to children, you need to obtain the first-level object and then return this object in reactive form. In this way, layer by layer attributes can be intercepted.

Listen for changes in the value of any property.

The easiest way is to use the Watch’s deep listening function.

watch (() = > reactive1, () = > {
  // Attribute value changed.
}, {deep:true})
Copy the code

In this way, the change of the attributes of any layer can be known, but there is a small problem, only one attribute value is known to have changed, but we do not know which attribute has changed. Both parameters are also new values, there are no old values.

So what if you had to know which property changed?

Use a proxy to trap a baby

Since you can do all kinds of interception in the Proxy, why not just go back and change any property?

Anyway, give Reactive a proxy and try again.

const myProxy = (_target, callback, arr) = > {
  const _arr = arr || []
  const proxy = new Proxy(_target, {
    get: function (target, key, receiver) {
      switch (key) {
        case '__v_isRef':
        case 'toJSON':
        case 'symbol':
        case 'Symbol(Symbol.toStringTag)':
          break;
        default:
          // Determine if it is an object
          if (typeof target[key] === 'object') {
            // console.log(' Get object ${key}! `, target[key])
            _arr.push(key)
            // Source monitor
            if (typeof callback === 'function') {
              callback('get', key, target[key], _arr)
            }
          } else if (typeofkey ! = ='symbol') {
            // console.log(' get attributes ', key, target[key])
          }
          break;
      }

      // Call the prototype method
      const res = Reflect.get(target, key, target)
      if (typeof res === 'object') {
        // Convert returned value into a proxy as well. we do the isObject check
        // here to avoid invalid value warning. Also need to lazy access readonly
        // and reactive here to avoid circular dependency.
        return myProxy(res, callback, _arr) / / recursion
      }
      return res
    },
    set: function (target, key, value, receiver) {
      if(key ! = ='__watch') {
        // Source monitor
        if (typeof callback === 'function') {
          callback('set', key, value, _arr)
        }
        Log (' path: ', _arr.join('-'))
        _arr.length = 0
        // console.log(' Set ${key} : ${value}! `)
      }
      
      // Call the prototype method
      return Reflect.set(target, key, value, target)
    }
  })
  
  // Return the instance
  return proxy
}

Copy the code

use


const ret3 = myProxy({
  a:'11'.b: {
    b1:' '.b2: {
      b21: {
        b211: '111'}},b3: {
      b31: {
        b311: '2222'}}}},(kind, key, value, path) = > {
  console.log('ret3 - Define end listener: [${kind}${key}- `, value, path)
})

const retChage = () = > {
  ret3.b.b2.b21.b211 = 'eeee'
}

Copy the code
  • callback

The old callback function that returns the property name and value.

  • _arr

Because nested attributes can be at many levels, and a set can only get the name of the last attribute, the process is all in GET. So I want to make an array and store the names of the properties at each level. It is true that I saved the attributes level by level when modifying them, but until I put RET3 into the template…

Templates also get values, trigger get events, and push property names into arrays.

So the question is, how do you tell the difference between a get triggered by a template and a GET triggered by an assignment to a property?

So far, no solution has been found.

In this case, only the last attribute is accurate, not the first one.

Toss about for a long time, just know some principles, but the original problem is not solved.

The deeper you go, the more complex the object structure, the more you use it in the template, the longer the data gets, so it’s almost useless.

If you just take the last property, and you don’t have an intermediate process, you can still use it for simple things, or specific things, but you can’t use it for general things.