手拉手带你了解Vue3的reactive和相关函数(一)

简介: 网上介绍 reactive 的各种资料也是非常多了,这里按照官网的介绍,对reactive以及相关的几个函数,做一个综合介绍,另外加一些扩展和稍微深入一点的内容。

网上介绍 reactive 的各种资料也是非常多了,这里按照官网的介绍,对reactive以及相关的几个函数,做一个综合介绍,另外加一些扩展和稍微深入一点的内容。


ES6的Proxy

Proxy是ES6提供的一个可以拦截对象基础操作的代理。因为 reactive 采用Proxy代理的方式,实现引用类型的响应性,所以我们先看看 Proxy 的基础使用方法,以便于我理解 reactive 的结构。我们先来定义一个函数,了解一下 Proxy 的基本使用方式:

// 定义一个函数,传入对象原型,然后创建一个Proxy的代理
const myProxy = (_target) => {
  // 定义一个 Proxy 的实例
  const proxy = new Proxy(_target, {
    // 拦截 get 操作
    get: function (target, key, receiver) {
      console.log(`getting ${key}!`, target[key])
      // 用 Reflect 调用原型方法
      return Reflect.get(target, key, receiver)
    },
    // 拦截 set 操作
    set: function (target, key, value, receiver) {
      console.log(`setting ${key}:${value}!`)
      // 用 Reflect 调用原型方法
      return Reflect.set(target, key, value, receiver)
    }
  })
  // 返回实例
  return proxy
}
// 使用方法,是不是和reactive有点像?
const testProxy = myProxy({
  name: 'jyk',
  age: 18,
  contacts: {
    QQ: 11111,
    phone: 123456789
  }
})
console.log('自己定义的Proxy实例:')
console.log(testProxy)
// 测试拦截情况
testProxy.name = '新的名字' // set操作 
console.log(testProxy.name) // get 操作

Proxy有两个参数target和handle。* target:要代理的对象,也可以是数组,但是不能是基础类型。* handler:设置要拦截的操作,这里拦截了set和get操作,当然还可以拦截其他操作。

我们先来看一下运行结果:

28.png


  • Handler 可以看到我们写的拦截函数 get 和 set;
  • Target 可以看到对象原型。

注意:这里只是实现了 get 和 set 的拦截,并没有实现数据的双向绑定,模板也不会自动更新内容,Vue内部做了很多操作才实现了模板的自动更新功能。


用 Proxy 给 reactive 套个娃,会怎么样?

有个奇怪的地方,既然Proxy可以实现对set等操作的拦截,那么 reactive 为啥不返回一个可以监听的钩子呢?为啥要用watch来实现监听的工作呢?为啥会这么想?看看Vuex4.0的设计,明明已经把state整体自动变成了 reactive 的形式,那么为啥还非得在 mutations 里写函数,实现 set 操作呢?好麻烦的样子。外部直接对 reactive 进行操作,然Vuex内部监听一下,这样大家不就都省事了吗?要实现插件功能,还是跟踪功能,不都是可以自动实现了嘛。所以我觉得还是可以套个娃的。


实现模板的自动刷新本来以为上面那个 myProxy 函数,传入 一个 reactive 之后,就可以自动实现更新模板的功能了,结果模板没理我。这不对呀,我只是监听了一下,不是又交给reactive了吗?为啥模板不鸟我?经过各种折腾,终于找到了原因,于是函数改成了这样:

/**
   * 用 Proxy定义一个 reactive 的套娃,实现可以监听任意属性变化的目的。(不包含嵌套对象的属性)
   * @param {*} _target  要拦截的目标
   * @param {*} callback 属性变化后的回调函数
   */
  const myReactive = (_target, callback) => {
    let _change = (key, value) => {console.log('内部函数')}
    const proxy = new Proxy(_target, {
      get: function (target, key, receiver) {
        if (typeof key !== 'symbol') {
          console.log(`getting ${key}!`, target[key])
        } else {
          console.log('getting symbol:', key, target[key])
        }
        // 调用原型方法
        return Reflect.get(target, key, receiver)
      },
      set: function (target, key, value, receiver) {
        console.log(`setting ${key}:${value}!`)
        // 源头监听
        if (typeof callback === 'function') {
          callback(key, value)
        }
        // 任意位置监听
        if (typeof _target.__watch === 'function') {
          _change(key, value)
        }
        // 调用原型方法
        return Reflect.set(target, key, value, target)
      }
    })
    // 实现任意位置的监听,
    proxy.__watch = (callback) => {
      if (typeof callback === 'function') {
        _change = callback
      }
    }
    // 返回实例
    return proxy
  }

代码稍微多了一些,我们一块一块看。

  • get 这里要做一下 symbol 的判断,否则会报错。好吧,其实我们似乎不需要 console.log。
  • set 这里改了一下最后一个参数,这样模板就可以自己更新了。
  • 设置 callback 函数,实现源头监听 设置一个回调函数,才能在拦截到set操作的时候,通知外部的调用者。只是这样只适合于定义实例的地方。那么接收参数的地方怎么办呢?

调用方法如下:

// 定义一个拦截reactive的Proxy
    // 并且实现源头的监听
    const myProxyReactive = myReactive(retObject,
      ((key, value) =>{
        console.log(`ret外部获得通知:${key}:${value}`)
      })
    )

这样我们就可以在回调函数里面得到修改的属性名称,以及属性值。这样我们做状态管理的时候,是不是就不用特意去写 mutations 里面的函数了呢?

  • 内部设置一个钩子函数 设置一个 _change() 钩子函数,这样接收参数的地方,可以通过这个钩子来得到变化的通知。

调用方法如下:

// 任意位置的监听
    myProxyReactive.__watch((key, value) => {
      console.log(`任意位置的监听:${key}:${value}`)
    })

只是好像哪里不对的样子。首先这个钩子没找到合适的地方放,目前放在了原型对象上面,就是说破坏了原型对象的结构,这个似乎会有些影响。然后,接收参数的地方,不是可以直接得到修改的情况吗?是否还需要做这样的监听?

最后,好像没有watch的deep监听来的方法,那么问题又来了,为啥Vuex不用watch呢?或者悄悄的用了?


深层响应式代理:reactive

说了半天,终于进入正题了。reactive 会返回对象的响应式代理,这种响应式转换是深层的,可以影响所有的嵌套对象。

注意:返回的是 object 的代理,他们的地址是相同的,并没有对object进行clone(克隆),所以修改代理的属性值,也会影响原object的属性值;同时,修改原object的属性值,也会影响reactive返回的代理的属性值,只是代理无法拦截直接对原object的操作,所以模板不会有变化。

这个问题并不明显,因为我们一般不会先定义一个object,然后再套上reactive,而是直接定义一个 reactive,这样也就“不存在”原 object 了,但是我们要了解一下原理。我们先定义一个 reactive 实例,然后运行看结果。

// js对象
const person = {
  name: 'jyk',
  age: 18,
  contacts: {
    QQ: 11111,
    phone: 123456789
  }
}
// person 的 reactive 代理 (验证地址是否相同)
const personReactive = reactive(person)
// js 对象 的 reactive 代理 (一般用法)
const objectReactive = reactive({
  name: 'jykReactive',
  age: 18,
  contacts: {
    QQ: 11111,
    phone: 123456789
  }
})
// 查看 reactive 实例结构
console.log('reactive', objectReactive )
// 获取嵌套对象属性
const contacts = objectReactive .contacts
// 因为深层响应,所以依然有响应性
console.log('contacts属性:', contacts)
// 获取简单类型的属性
let name = objectReactive.name 
// name属性是简单类型的,所以失去响应性
console.log('name属性:', name)

运行结果:


29.png


  • Handler:可以看到Vue除重写set和get外,还重写了deleteProperty、has和ownKeys。
  • Target:指向一个Object,这是建立reactive实例时的对象。

属性的结构:

30.png


然后再看一下两个属性的打印结果,因为contacts属性是嵌套的对象,所以单独拿出来也是具有响应性的。而name属性由于是string类型,所以单独拿出来并不会自动获得响应性,如果单独拿出来还想保持响应性的话,可以使用toRef。

注意:如果在模板里面使用{{personReactive.name}}的话,那么也是有响应性的,因为这种用法是获得对象的属性值,可以被Proxy代理拦截,所以并不需要使用toRef。如果想在模板里面直接使用{{name}}并且要具有响应性,这时才需要使用toRef。


相关文章
|
14天前
|
缓存 JavaScript UED
Vue3中v-model在处理自定义组件双向数据绑定时有哪些注意事项?
在使用`v-model`处理自定义组件双向数据绑定时,要仔细考虑各种因素,确保数据的准确传递和更新,同时提供良好的用户体验和代码可维护性。通过合理的设计和注意事项的遵循,能够更好地发挥`v-model`的优势,实现高效的双向数据绑定效果。
117 64
|
14天前
|
前端开发 JavaScript 测试技术
Vue3中v-model在处理自定义组件双向数据绑定时,如何避免循环引用?
Web 组件化是一种有效的开发方法,可以提高项目的质量、效率和可维护性。在实际项目中,要结合项目的具体情况,合理应用 Web 组件化的理念和技术,实现项目的成功实施和交付。通过不断地探索和实践,将 Web 组件化的优势充分发挥出来,为前端开发领域的发展做出贡献。
24 8
|
13天前
|
存储 JavaScript 数据管理
除了provide/inject,Vue3中还有哪些方式可以避免v-model的循环引用?
需要注意的是,在实际开发中,应根据具体的项目需求和组件结构来选择合适的方式来避免`v-model`的循环引用。同时,要综合考虑代码的可读性、可维护性和性能等因素,以确保系统的稳定和高效运行。
19 1
|
13天前
|
JavaScript
Vue3中使用provide/inject来避免v-model的循环引用
`provide`和`inject`是 Vue 3 中非常有用的特性,在处理一些复杂的组件间通信问题时,可以提供一种灵活的解决方案。通过合理使用它们,可以帮助我们更好地避免`v-model`的循环引用问题,提高代码的质量和可维护性。
25 1
|
14天前
|
JavaScript
在 Vue 3 中,如何使用 v-model 来处理自定义组件的双向数据绑定?
需要注意的是,在实际开发中,根据具体的业务需求和组件设计,可能需要对上述步骤进行适当的调整和优化,以确保双向数据绑定的正确性和稳定性。同时,深入理解 Vue 3 的响应式机制和组件通信原理,将有助于更好地运用 `v-model` 实现自定义组件的双向数据绑定。
|
JSON 前端开发 API
Vue3入门到精通--reactive以及reactive相关函数
Vue3入门到精通--reactive以及reactive相关函数
|
17天前
|
JavaScript API 开发者
Vue是如何进行组件化的
Vue是如何进行组件化的
|
19天前
|
JavaScript 前端开发 开发者
vue 数据驱动视图
总之,Vue 数据驱动视图是一种先进的理念和技术,它为前端开发带来了巨大的便利和优势。通过理解和应用这一特性,开发者能够构建出更加动态、高效、用户体验良好的前端应用。在不断发展的前端领域中,数据驱动视图将继续发挥重要作用,推动着应用界面的不断创新和进化。
|
20天前
|
JavaScript 前端开发 开发者
vue学习第一章
欢迎来到我的博客!我是瑞雨溪,一名热爱前端的大一学生,专注于JavaScript与Vue,正向全栈进发。博客分享Vue学习心得、命令式与声明式编程对比、列表展示及计数器案例等。关注我,持续更新中!🎉🎉🎉
23 1
vue学习第一章
|
20天前
|
JavaScript 前端开发 索引
vue学习第三章
欢迎来到瑞雨溪的博客,一名热爱JavaScript与Vue的大一学生。本文介绍了Vue中的v-bind指令,包括基本使用、动态绑定class及style等,希望能为你的前端学习之路提供帮助。持续关注,更多精彩内容即将呈现!🎉🎉🎉
22 1
vue学习第三章