reactive是如何实现深层响应的?

简介: 看过官网文档的都知道,Vue3 的响应性分为浅层和深层,我们常用的 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?


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


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


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


image.png


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


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


相关文章
|
6月前
第17节:Vue3 反应式代理与原始代理
第17节:Vue3 反应式代理与原始代理
46 0
|
1月前
|
前端开发 Java API
vertx学习总结5之回调函数及其限制,如网关/边缘服务示例所示未来和承诺——链接异步操作的简单模型响应式扩展——一个更强大的模型,特别适合组合异步事件流Kotlin协程
本文是Vert.x学习系列的第五部分,讨论了回调函数的限制、Future和Promise在异步操作中的应用、响应式扩展以及Kotlin协程,并通过示例代码展示了如何在Vert.x中使用这些异步编程模式。
44 5
vertx学习总结5之回调函数及其限制,如网关/边缘服务示例所示未来和承诺——链接异步操作的简单模型响应式扩展——一个更强大的模型,特别适合组合异步事件流Kotlin协程
|
12天前
|
监控 JavaScript 前端开发
前端的混合之路Meteor篇(六):发布订阅示例代码及如何将Meteor的响应数据映射到vue3的reactive系统
本文介绍了 Meteor 3.0 中的发布-订阅模型,详细讲解了如何在服务器端通过 `Meteor.publish` 发布数据,包括简单发布和自定义发布。客户端则通过 `Meteor.subscribe` 订阅数据,并使用 MiniMongo 实现实时数据同步。此外,还展示了如何在 Vue 3 中将 MiniMongo 的 `cursor` 转化为响应式数组,实现数据的自动更新。
|
4月前
|
前端开发 JavaScript 算法
react【框架原理详解】JSX 的本质、SyntheticEvent 合成事件机制、组件渲染过程、组件更新过程
react【框架原理详解】JSX 的本质、SyntheticEvent 合成事件机制、组件渲染过程、组件更新过程
68 0
|
6月前
|
前端开发
AJAX发送请求方法封装和请求函数底层刨析以及axios二次封装
AJAX发送请求方法封装和请求函数底层刨析以及axios二次封装
|
6月前
|
存储 JavaScript 前端开发
详细介绍Vue3中的监听属性的概念、使用方式以及一些常见的应用场景
详细介绍Vue3中的监听属性的概念、使用方式以及一些常见的应用场景
310 0
|
开发框架 JavaScript 前端开发
web前端面试高频考点——Vue原理(理解MVVM模型、深度/监听data变化、监听数组变化、深入了解虚拟DOM)
web前端面试高频考点——Vue原理(理解MVVM模型、深度/监听data变化、监听数组变化、深入了解虚拟DOM)
247 0
|
前端开发
前端学习案例16-promise的理解方式&调用机制3
前端学习案例16-promise的理解方式&调用机制3
71 0
前端学习案例16-promise的理解方式&调用机制3
|
前端开发
前端学习案例15-promise的理解方式&调用机制2
前端学习案例15-promise的理解方式&调用机制2
77 0
前端学习案例15-promise的理解方式&调用机制2