Vue3响应式原理

151 阅读4分钟

问题

1、Vue3的响应式原理和Vue2有什么不同?

  • Vue3是使用Proxy,Proxy可以劫持整个data对象,然后递归返回属性的值的代理即可实现响应式;但兼容性不是很好;
  • Vue2使用的是Object.defineProperty,它只能劫持对象的属性,所以它需要深度遍历data中的每个属性,这种方式对于数组很不友好,而且对象观测后,新增的属性就不是响应式的,不过可以通过this.$set()或者Vue.set()来添加新的属性;

2、什么是数据响应式?

答:数据是可以进行观测的,也就是说在读取和设置的时候可以劫持它来做其他操作;
注意:数据响应式和视图更新是没有关系的!!!响应式只是一种机制,一种数据变化的侦测机制,实现这种机制的方法也不是唯一的,就例如vue2和vue3实现响应式的方法是不同的

vue2使用Object.defineProperty实现响应式

  • 遍历属性,对每一个属性的值用Object.defineProperty进行getter和setter的改造;

image.png

Proxy和Reflect

1、Proxy

  • Proxy对象对于创建一个对象的代理,也可以理解成在对象前面设了一层拦截,可以实现基本操作的拦截和一些自定义操作(比如一些赋值、属性查找、函数调用等);
  • 用法:var proxy = new Proxy(target, handler);target: 目标函数(即进行改造的函数);handler: 一些自定义操作(比如vue中getter和setter的操作);

2、Reflect

  • Reflect是es6为操作对象而提供的新API,设计它的目的有:
    ① 把Object对象上一些明显属于语言内部的方法放到Reflect对象身上,比如Object.defineProperty;
    ② 修改了某些object方法返回的结果;
    ③ 让Object操作都变成函数行为;
    ④ Reflect对象上的方法和Proxy对象上的方法一一对应,这样就可以让Proxy对象方便地调用对应的Reflect方法;
  • Reflect.get(target, propertyKey, receiver)等价于target[propertyKey] Reflect.get方法查找并返回target对象的propertyKey属性,如果没有该属性,则返回undefined。
  • Reflect.set(target, propertyKey, value, receiver)等价于target[propertyKey] = value Reflect.set方法设置target对象的propertyKey属性等于value。

3、Proxy和Reflect的使用

image.png

使用Proxy和Reflect完成响应式

  • reactive是一个返回Proxy对象的函数; image.png

详情参考:www.jianshu.com/p/8c95e0868…

reactive

可以使用 reactive() 函数创建一个响应式对象或数组:

import { reactive } from 'vue'

const state = reactive({ count: 2, obj: { count: 4, obj: { count: 5 } } })

深层响应性

在 Vue 中,状态都是默认深层响应式的。这意味着即使在更改深层次的对象或数组,你的改动也能被检测到 const addCount = function(){ state.obj.obj.count ++ }

响应式代理 vs. 原始对象

值得注意的是,reactive() 返回的是一个原始对象的 Proxy,它和原始对象是不相等的:

`const row = {}

const proxyRow = reactive(row)

console.log(row === proxyRow) //false`

为保证访问代理的一致性,对同一个原始对象调用 reactive() 会总是返回同样的代理对象,而对一个已存在的代理对象调用 reactive() 会返回其本身:

console.log(reactive(row) === proxyRow)//true

这个规则对嵌套对象也适用。依靠深层响应性,响应式对象内的嵌套对象依然是代理:

const proxy = reactive({})

const raw = {}

proxy.nested = raw console.log(proxy.nested === raw) // false

reactive() 的局限性

  1. 仅对对象类型有效(对象、数组和 MapSet 这样的集合类型),而对 stringnumber 和 boolean 这样的 原始类型 无效。
  2. 因为 Vue 的响应式系统是通过属性访问进行追踪的,因此我们必须始终保持对该响应式对象的相同引用。这意味着我们不可以随意地“替换”一个响应式对象,因为这将导致对初始引用的响应性连接丢失:

image.png 同时这也意味着当我们将响应式对象的属性赋值或解构至本地变量时,或是将该属性传入一个函数时,我们会失去响应性:

image.png

用 ref() 定义响应式变量

reactive() 的种种限制归根结底是因为 JavaScript 没有可以作用于所有值类型的 “引用” 机制。为此,Vue 提供了一个 ref() 方法来允许我们创建可以使用任何值类型的响应式 ref

import { ref } from 'vue'

const count = ref(0)

ref() 将传入参数的值包装为一个带 .value 属性的 ref 对象:

image.png

和响应式对象的属性类似,ref 的 .value 属性也是响应式的。同时,当值为对象类型时,会用 reactive() 自动转换它的 .value

一个包含对象类型值的 ref 可以响应式地替换整个对象:

const obj = ref({count:1})

obj.value = {count:16}

ref 被传递给函数或是从一般对象上被解构时,不会丢失响应性:

image.png

简言之,ref() 让我们能创造一种对任意值的 “引用”,并能够在不丢失响应性的前提下传递这些引用。这个功能很重要,因为它经常用于将逻辑提取到 组合函数 中。

image.png