前言
在前端开发过程中,最为注重的就是数据的即时性和响应。但随着技术的发展vue2.0的数据响应方式,不能响应属性的新增和删除、以及通过数组下标修改界面不会自动更新等弊端逐渐显露。vue3.0为开发者提供了更为便捷的数据响应方式,接下来就让我们一起去探索一下。

目标

1 基本数据类型与复杂数据类型如何定义响应式数据
2 ref、reactive数据响应式原理
3 vue2.0数据响应方式与弊端
4. proxy数据响应原理


数据响应式 学习vue3.0数据响应 基本用法与用到的底层方法 proxy如何实现数据代理 vue3.0为什么使用reflect对源对象属性操作 vue2.0与vue3.0数据响应原理总结 回顾vue2.0数据响应式 vue3.0数据响应式

学习vue3.0数据响应

1 基本用法与用到的底层方法

1.1 ref

ref 用于将基本类型数据变成响应式

import {ref} from 'vue'

setup(){
    // ref处理数据
    let count = ref(0)
    let name = ref('张三')
    function addCount(){
         // 修改数据要用.value,
         // 模版字符串中不用.value  因为vue3 自动的给我们做了处理
         count.value++ 
         console.log('---------count---------',count)
         console.log('---------name---------',name)
    }
    return{
        count,
        name,
        addCount
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.

经过ref处理过后的基本数据类型,数据会包裹在RefImpl中,当数据发生改变就会触发propotype下get、set ==》 底层Object.definepropoty的getter setter
[Vue3] - 3 数据响应式_响应式

ref不能定义复杂类型数据吗,废话不多,直接上手来try一下
import {ref} from 'vue'
setup(){
    let person = ref({
            name:'张三',
            age:'1111',
            sex:'男'
    })
    function addCount(){
            console.log('---修改前person---',person,person.value,person.value.name)
            person.value.name='小明'
            console.log('---修改后person---',person,person.value,person.value.name)
    }
    return{count,name,addCount}
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.

结果是页面中的数据是响应式的,说明ref是可以处理复杂数据类型
既然ref已可以满足,vue3为啥还设计了一个reactive方法?
看一下控制台打印的先是RefImpl RefImpl .value才是一个Proxy对象
reactive处理后的数据 是proxy对象(数据响应的底层原理是ES6的proxy),ref底层也是自动的把复杂数据类型进行了reactive处理

总结来说ref底层还是用reactive对复杂数据对象进行了处理。
[Vue3] - 3 数据响应式_数据_02

1.2 reactive

reactive专门用来处理复杂数据对象的方法

import { ref,reactive } from 'vue'

  setup(){
        let count = ref(0)
        let name = ref('张三')
        let person = reactive({
            name:'张三',
            age:'1111',
            sex:'男',
            school:{
                name:'北京小学',
                class:{
                    id:'一年级'
                }
            }
        })
        function addCount(){
            count.value++
            console.log('---修改前person---',person,person,person.name)
            person.school.class.id ='小学'
            console.log('---修改后person---',person,person,person.name)
        }
        return{
            count,
            name,
            person,
            addCount
        }
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.

reactive数据响应式底层实现原理是es6的proxy
[Vue3] - 3 数据响应式_插入图片_03

reactive可以处理基本数据吗,废话不多,直接上手来try一下
let count = reactive(0)
let name = reactive('张三')
  • 1.
  • 2.

定义完成,控制台直接报错,reactive无法处理基本数据
[Vue3] - 3 数据响应式_插入图片_04

1.3 reactive与ref对比

从定义数据角度

  • ref用来定义:基本数据类型数据
  • reactive:对象(或数据)类型数据
  • ref可定义对象(或数据)类型数据,但它内部会自动通过reactive转为代理对象
    从原理角度
  • ref通过Object.definePropertygetset来数显响应式(数据劫持)
  • reactive通过使用Proxy来实现响应式(数据劫持),再通过Reflect操作源对象内部数据
    从使用角度
  • ref定义的数据:操作数据需要.value,读取数据时模板中直接读取不需要.value
  • reactive定义的数据:.key .键名就可使用

2 proxy数据代理

Proxy是es6中提供的一个代理方法,可以在js中直接使用无需引用
[Vue3] - 3 数据响应式_数据_05
下面我们用proxy来手写一个 模拟一下vue3.0实现响应式

将对象封装到proxy中,当对对象中的属性进行增删改查时,会触发Proxy下的get、set以及deleteProperty,这样就能就监听到 数据的变化,从而更新数据变化实现数据响应式。

<script>
      let person = {
        name:'张三',
        age:"18"
      }
      
     let p = new Proxy(person,{
        // 读取属性时调用
        get(target,key){
          console.log(`-----获取${key}的值-----`)
          return target[key]
        },
        // 修改添加属性时调用
        set(target,key,value){
          console.log(`-----修改/添加${key}的值为${value}-----`)
          // 操作原数据
          target[key] = value
          return true
        },
        // 删除属性时调用
        deleteProperty(target,key){
          console.log(`-----删除${key}-----`)
          return delete target[key]
        }
     })
</script>
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.

Object.definePropety()相比Proxy无法监听到属性增加和删除,Proxy对属性的任何操作都能监听到

[Vue3] - 3 数据响应式_响应式_06

3 为什么使用reflect对源对象属性操作

上面 模拟的实现了vue3.0数据响应式,但是vue3.0真正的底层却不是这样写的,它借助了Reflect(反射)来处理数据。

Reflect是什么?Reflect是ES6提供的一种数据反射方法,很多Object的方法在Reflect上都可以使用,例如defineProperty。
[Vue3] - 3 数据响应式_响应式_07
Vue3.0中用ProxyReflect 实现数据的响应式
使用Reflect去更改对象的值

let person = {
    name:'张三',
    age:"18"}
let p = new Proxy(person,{
        // 读取属性时调用
        get(target,key,receiver){
          console.log(`-----获取${key}的值-----`,receiver==p)
          return Reflect.get(target,key)
        },
        // 修改添加属性时调用
        set(target,key,value){
          console.log(`-----修改/添加${key}的值为${value}-----`)
          Reflect.set(target,key,value)
        },
        // 删除属性时调用
        deleteProperty(target,key){
          console.log(`-----删除${key}-----`)
          return Reflect.deleteProperty(target,key) 
        }
})
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.

既然不用Reflect已经能实现数据响应式了,为什么还要多此一举用Reflect来处理数据?
下面就从一下两个方面来具体说明

3.1 健壮性
      Object.defineProperty(person,'SEX',{
        get(){
          return '男'
        },
        
      })
      Object.defineProperty(person,'SEX',{
        get(){
          return '女'
        },
      })
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.

[Vue3] - 3 数据响应式_数据_08
可以看到,用Object.defineProperty重复的定义属性的时候 报错了,且因为JS是单线程的,导致程序整个挂断,这不是我们想看到的情况,但是若在底层中写大量的 try catch 不太优雅。
如何解决这个问题呢?,Reflect可以完美的解决这个问题,我们来看一下

const sex1 = Reflect.defineProperty(person,'SEX',{
        get(){
          return '男'
        },
      })
const sex2 = Reflect.defineProperty(person,'SEX',{
        get(){
          return '女'
        },
      })
console.log(sex1)
console.log(sex2)
if(sex2){
   console.log('sex2为true,执行成功')
   // 业务逻辑
}else{
   console.log('sex2为false,执行失败')
   // 业务逻辑
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.

Reflect重复定义不会导致整个程序挂掉,且Reflect.defineProperty会返回一个是否执行成功的Boolean值,可以根据此值进行相对应的业务处理。

3.2 改变this的上下文指向

[Vue3] - 3 数据响应式_响应式_09
Proxy的get、set有一个参数receiver,receiver究竟指的是什么?
通过console.log可看出 receiver==p p是Proxy的实例对象,receiver指向了Proxy的实例对象,即调用的指向
Reflect.get中有第三个参数receiver,Reflect.get(target,key,receiver)可以将访问对象的this指向修改为receiver对象

      let p = new Proxy(person,{
        // 读取属性时调用
        get(target,key,receiver){
          console.log(`-----get receiver是否等于p-----`,receiver==)
          return Reflect.get(target,key,receiver)
        },
        // 修改添加属性时调用
        set(target,key,value,receiver){
          console.log(`-----set receiver是否等于p-----`,receiver==p)
          Reflect.set(target,key,value,receiver)
        },
      })
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.

[Vue3] - 3 数据响应式_插入图片_10

vue2.0与vue3.0数据响应原理总结

1 回顾vue2.0数据响应式

vue2数据响应式原理可参考 vue2数据检测原理
实现原理

  1. 对象类型: 通过Object.definePropety()对属性的读取、修改进行拦截(数据劫持)
  2. 数组类型: 通过重写更新数组的一系列方法来实现拦截。(对数组的变更方法进行包装)
   Object.defineProperty(data,'count',{
      get () ,
      set () {}
   })
  • 1.
  • 2.
  • 3.
  • 4.
  1. 其他: Vue.set()this.$set()

存在问题:

  1. 新增属性、删除属性,界面不会更新
  2. 直接通过下标修改数组,界面不会自动更新

2 Vue3.0的响应式

实现原理

  1. Proxy (代理) 拦截对象中任意属性的变化,包括: 属性值的读写、属性的添加、属性的删除等
  2. 通过Reflect(反射)对象的属性进行操作. 。
  3. MDN文档中描述的Proxy与Reflect:  Proxy

总结

  1. ref一般用于基本数据类型,也可以用于复杂数据类型,但还是建议复杂直接使用reactive。
  2. reactive用于复杂数据类型实现响应式,不可以用于基本数据类型。
  3. Proxy (代理)和Reflect可以拦截对象中属性的增删改查,比vue2.0中的Object.definePropety更加方便灵活。

到这里有关vue3.0的响应式原理已经结束,可能看完本文还是有点模糊迷瞪,没关系你可以试着敲一下代码。自己尝试一下会有更加深刻的理解

参考链接
 vue2数据检测原理