在Vue 中应用选项式API ,去申明响应式对象是很简略的。咱们只须要将须要响应式的变量属性放进 data 函数中,并return 进去。 Vue 框架会帮咱们把数据变为响应式,并在模板中可用。 如上面所示。

export defaut {    // other        data() {         return {             hello: 'world'         }     }        //... other code}

但对于 Composition API,事件就没那么简略了。状态申明必须应用Ref 和 Reactive 函数来实现,这两个函数的行为表现也不一样。

让咱们讨论一下 Vue 3 中产生了什么变动以及为什么咱们须要两个不同的助手。

Vue3 中的响应式实现

大家都晓得Vue2 中,是通过Object.defineProperty 实现的依赖收集和响应式解决。但在Vue3 应用了javascript 的Proxy API 齐全重写了响应式外围。

Proxy 是一种更古代更优雅的API 能够实现对一个对象的代理和劫持。

你能够通过下边一段代码来理解Proxy 是如何工作的:

const userInfo = {  name: "sean",  age: 35,};const handler = {  get(target, property) {    if (property === "name") {      const name = target[property]      return name.charAt(0).toUpperCase() + name.slice(1);    }    if (property === "age") {      return '--'    }    return target[property]  },};const proxyObj = new Proxy(userInfo, handler);console.log(proxyObj.name) // "Sotis"console.log(proxyObj.age) // "--"

handler 内的 get 办法称为 trap ,每次拜访对象的属性时都会调用该办法。

同样的情理,咱们很容易就能推断出一个 set 的 trap 办法。

const userInfo = {  name: "Sean",  age: 35,};const handler = {  set(target, prop, value) {    if (prop === "age") {      if (!Number.isInteger(value)) {        throw new TypeError("age 类型谬误");      }      if (value > 200) {        throw new RangeError("额,如同超过了范畴了");      }    }    target[prop] = value;    return true;  },};const proxy = new Proxy(userInfo, handler);proxy.age = 12 // OKproxy.age = 300 // Error: 额,如同超过了范畴了

这就是Vue3 的响应式实现的外围原理,当咱们应用Reactive 工具办法申明响应式属性的时候, 框架底层会通过Proxy 来实现,属性的变更追踪和依赖解决。

function reactive(obj) {  return new Proxy(obj, {    get(target, key) {      track(target, key)      return target[key]    },    set(target, key, value) {      target[key] = value      trigger(target, key)    }  })}

当然,Vue 框架中的响应式,实现要比这简单的多。 框架须要解决很多边界状况。 然而外围的原理还是应用了Proxy。 如果你对Vue 的Reactive的具体实现感兴趣,请看如下连贯。

https://github.com/vuejs/core/blob/main/packages/reactivity/src/reactive.ts#L83

下面的代码片段解释了为什么将响应式变量解构或重新分配给局部变量不再具备响应式的个性,因为它不再触发源对象上的 get/set 代理track。

这看起来是一个让所有变得响应式的完满解决方案。然而有一个问题!依据定义,Proxy 仅实用于简单类型(对象、数组、映射和汇合)。

想要将原始值( Boolean、Number、BigInt、String、Symbol、undefined 和 null 等类型的值) 变成响应式数据,就必须对其做一层包裹,也就是咱们接下来要介绍的 ref。

function ref(value) {  const refObject = {    get value() {      track(refObject, 'value')      return value    },    set value(newValue) {      value = newValue      trigger(refObject, 'value')    }  }  return refObject}

通过将原始值,进行包装成Object 咱们实现了, 原始值的响应式。

这也就解释了为什么必须在脚本设置中应用的烦人的 .value。

同样,重组或重新分配给局部变量是行不通的。

总结

所以为什么同时须要 Ref 和 Reactive 的答案是:

  • 代理。对于简单类型,能够间接应用它们,但对于原始类型数据须要创立代理对象。

心愿理解 Vue 的底层工作原理能够让您更加高效,并打消 ref 和 Reactive 之间的混同。