程序员Sunday头像

Vue 3 响应式原理是什么?Proxy、ref 和 reactive 有什么区别?

Vue 3 响应式原理是什么?Proxy、ref 和 reactive 有什么区别?

Vue 3 的 reactive 为对象创建响应式代理。依赖执行时读取属性,系统记录这段逻辑依赖哪个属性;属性改变后,再通知相关依赖更新。

Proxy 负责拦截操作,依赖收集和触发机制负责建立对应关系。不是任意一次读取都会登记,也不是每次修改都让整页立即重新渲染,组件更新还会经过调度。

ref 则用带 .value 的包装,让基本类型和对象都能作为响应式值使用。reactive 更适合直接组织对象属性,ref 也方便整体替换值。

解构 reactive 中的基本类型属性,会得到当时的普通值,后续访问不再经过原代理。需要保留与原属性的连接时,可以使用 toRef 或 toRefs。对象属性的解构要另外看它是否仍然指向响应式对象。

Vue 响应式,读取时记录,修改时通知

拦截读写,怎样变成更新页面?

先建立一个具体关系

假设页面显示 state.count。组件渲染时,会读取这个属性。

Vue 在这一轮渲染逻辑执行期间,能够知道当前有哪些属性被使用。于是它记下来,“这个渲染逻辑需要 count。”

以后 count 改变,就能找到需要重新执行的逻辑。如果另一段逻辑只读 name,它不应该仅仅因为 count 改了就成为 count 的直接依赖。

这就是“依赖收集”要解决的事情。只说 Proxy 可以拦截,还没有说明页面与属性怎样建立关系。

读取时记录,修改时通知

可以把主要过程理解成三步,

  1. 某段需要跟踪的逻辑开始执行,成为当前活跃的依赖。
  2. 它读取代理属性,系统记录对象、属性与依赖之间的关系。
  3. 属性改变后,系统找到相关依赖,触发或调度它们重新执行。

Vue 官方响应式原理给出了 track 与 trigger 的教学示意。实际实现还需要处理嵌套执行、过期依赖清理、数组与集合等情况,不能拿几行伪代码当完整源码。

如果代码只在普通函数里随手读了一次属性,而没有处于依赖收集的执行过程中,就不能认为这个函数以后会自动再次运行。

reactive 和 ref,不只是对象与数字的区别

reactive 返回对象代理。对属性读写,才能经过代理的拦截。如果还拿原始对象直接修改,就绕开了这条入口。

ref 返回一个带 .value 的容器,对这个值的读取与修改也能被跟踪。它可以保存数字,也可以保存对象;对象值通常会经过响应式转换,shallowRef 等 API 则有不同约定。

因此,“对象一定用 reactive,数字一定用 ref”只是一种简化习惯,不是能力边界。

如果经常需要替换整个值,比如请求完成后把结果列表整体换掉,ref 的 .value = 新值 往往比较直接。reactive 则更适合保留一个代理对象,修改其中属性。

解构后为什么有时不更新?

看一个 Vue 3 局部示例,

import { reactive, toRef } from "vue";

const state = reactive({ count: 0 });
const { count } = state;          // 当时读出的普通数字
const linkedCount = toRef(state, "count");

state.count = 2;
console.log(count);               // 0
console.log(linkedCount.value);   // 2

linkedCount.value = 3;
console.log(state.count);         // 3

普通数字被取出来以后,变量与原属性已经不是同一个可追踪入口。toRef 则保留了指向该属性的连接。

不过,如果解构的是一个响应式对象引用,继续修改那个对象的属性,仍然可能触发响应式。问题是“变量绑定是否与原属性相连”,不能简单背成“只要解构就彻底没响应式”。

这里的代码针对 Vue,不提供不同语言里名为 Proxy 的对象来冒充同一机制。

普通解构与 toRef,连接不同

数据改变了,DOM 为什么还没立即改变?

响应式通知和 DOM 已更新,是两个时间点。

Vue 通常把组件更新放进调度队列,合并同一轮中的多个变化,避免每修改一个值就立刻重复更新。需要在更新后读取 DOM,可以使用 nextTick 等待相应更新完成。

computed 则是在响应式系统上建立派生值。它会跟踪所读取的依赖,并按缓存与求值规则工作,不是普通函数换了一个名字。

讲到这里,才能从数据读写一路解释到组件更新,而不是只回答“Vue 3 用了 Proxy”。

面试官继续追问

Proxy 能直接代理数字吗?

不能直接以基本类型作为 Proxy 的目标对象。ref 提供了包装入口,使数字等值也能进入响应式系统。

reactive 返回值等于原对象吗?

引用身份不同。代理虽然表现得像原对象,但 proxy === original 通常为 false。项目中最好统一通过代理操作,避免混用导致追踪问题。

模板里为什么常常不用写 .value?

Vue 模板提供了 ref 解包规则,但规则有具体适用条件。JavaScript 代码中操作 ref,通常仍然使用 .value,不能把模板便利理解成容器不存在了。

面试速记卡

  • Proxy,拦截操作,不独自完成整套响应式。
  • 读取阶段,在依赖执行时记录对象、属性与依赖的关系。
  • 修改阶段,通知相关依赖,组件更新还要经过调度。
  • ref 与 reactive,容器值和对象代理,都有适合的组织方式。
  • 解构边界,基本类型会断开属性连接,可用 toRef 保留连接。

我是程序员Sunday。本文收录于 sunday面试指南,原文与配图可继续阅读。

转载自 CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/u011068996/article/details/167704340

文章来源转载

评论

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--