zoukankan      html  css  js  c++  java
  • reactive是如何实现深层响应的?

    深层响应的 reactive

    看过官网文档的都知道,Vue3 的响应性分为浅层和深层,我们常用的 reactive 是深层的。

    我们也都知道,reactive 是使用 proxy 来实现响应性的,那么问题来了:
    既然 proxy 的拦截操作是浅层的,对于嵌套属性的操作无感,那么 reactive 是如何实现深层响应的呢?

    这个就得看看 源码了。

    // 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.
                const shouldUnwrap = !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);  // 重点在这里。。。
            }
            return res;
        };
    }
    
    

    这是拦截 get 操作的代码。
    上面的可以跳过,直接看倒数第二个 return。

    简单地说,各种判断后,返回一个新的 reactive。

    就是说,给子子属性赋值的时候,需要先获取第一级的对象,然后把这个对象变成 reactive 的形式返回,这样就可以实现层层属性的拦截了。

    监听任意属性的值的变化。

    最简单的方式就是用 watch 的深度监听功能。

    watch (() => reactive1, () => {
      // 属性值变了。
    }, {deep:true})
    

    这样任意一层的属性的变化,都可以获知,只是有个小问题,只知道有属性值变了,但是不知道具体是哪个属性变了。两个参数也都是新值,没有旧值了。

    那么如果一定要知道是哪个属性变了呢?

    用 proxy 套个娃

    既然 Proxy 里面可以进行各种拦截,那么为啥不顺便返回来改了哪个属性呢?

    不管那么多了,自己给 reactive 套个 proxy 再次拦截试一试。

    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:
              // 判断是不是对象
              if (typeof target[key] === 'object') {
                // console.log(`获取 对象 ${key}!`, target[key])
                _arr.push(key)
                // 源头监听
                if (typeof callback === 'function') {
                  callback('get', key, target[key], _arr)
                }
              } else if (typeof key !== 'symbol') {
                // console.log('获取 属性 ', key, target[key])
              }
              break;
          }
    
          // 调用原型方法
          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) // 递归
          }
          return res
        },
        set: function (target, key, value, receiver) {
          if (key !== '__watch') {
            // 源头监听
            if (typeof callback === 'function') {
              callback('set', key, value, _arr)
            }
            // console.log('路径:', _arr.join('-'))
            _arr.length = 0
            // console.log(`设置 ${key}:${value}!`)
          }
          
          // 调用原型方法
          return Reflect.set(target, key, value, target)
        }
      })
      
      // 返回实例
      return proxy
    }
    
    

    使用方式

    
    const ret3 = myProxy({
      a:'11',
      b: {
        b1:'',
        b2: {
          b21: {
            b211: '111'
          }
        },
        b3: {
          b31: {
            b311: '2222'
          }
        }
      }
    }, (kind, key, value, path) => {
      console.log(`ret3 - 定义端监听:【${kind}】 ${key}-`, value, path)
    })
    
    const retChage = () => {
      ret3.b.b2.b21.b211 = 'eeee'
    }
    
    
    • callback
      古老的回调函数,把属性名称和属性值返回来就好。

    • _arr
      因为嵌套属性可能是很多级别的,而 set 只能获知最后一个属性的名称,中间的过程全在 get 里面。
      于是就想做个数组把每一级的属性名称存进去。
      修改属性的时候也确实是一级一级的存进去了,但是直到我把 ret3 放到了模板里面……

    模板里面也是要获取值的,也会触发 get 事件,也会往数组里面 push 属性名称。

    于是问题来了,如何区分是模板触发的 get 还是给属性赋值触发的 get?

    到目前为止还是没有想到办法。

    这样的话,就只有最后一个属性是准确的,前面的就不一定了。

    折腾半天,只是知道了一些原理,但是最初的问题还是没有解决。

    监听结果

    层次越深,对象结构越复杂,模板里用的越多,这个数据就越长,所以基本没啥用了。

    只拿到最后一个属性,没有中间过程的话,对于简单的,或者特定的还是可以用用的,但是想通用就基本没戏了。

  • 相关阅读:
    Flask——中间件,蓝图
    导出项目环境 requiredments
    Flask——路由正则,Request方法,Response方法,cookies,session,闪现,请求扩展
    Flask——路由,jinja2,三板斧,前端数据传输,反向解析,配置文件,转换器,路由本质,CBV
    Linux——权限,二进制安装,进程管理,系统服务
    Python基础概括
    echarts 使用
    Celery 使用
    第五周总结
    软件的需求为什么会出现差别
  • 原文地址:https://www.cnblogs.com/jyk/p/14670393.html
Copyright © 2011-2022 走看看