Skip to content

Vue 3 的响应式怎么实现? ​

Vue 3 用 Proxy 拦截对象的读取和写入。读取时把当前正在运行的效果记录下来,写入时把这些效果重新执行。组件渲染就是一个效果,所以用到的数据一变,组件会再渲染。

原理 ​

reactive(obj) 返回深层代理。效果函数运行期间,get 调用 track(target, key) 收集依赖;set 调用 trigger(target, key) 通知它们。没被读过的属性,后来变了也不会通知任何人。

js
const state = reactive({ count: 0 })
// 渲染时读了 state.count,这次渲染就订阅了 count

ref 用一个带 value 的对象装原始值,因为 Proxy 只能代理对象。模板里的 ref 会自动解包,<script> 里要写 .value。把 ref 放进 reactive 对象再读取时,也会自动解包。

computed 懒计算:依赖变了只把结果标成脏,下次读取才重算,并把自己注册成依赖的订阅者。watch 追踪指定数据源并跑副作用;watchEffect 立刻执行一次,自动收集里面读到的数据。

Vue 2 用 Object.defineProperty 在已有的键上拦截。后加的属性、按下标改数组,默认观察不到,要走 Vue.set 或数组的变更方法。Proxy 可以拦截新增、删除和更多数组操作。

Vue 3.5 起,模板编译会保持对解构后的 props 的响应式。更早的版本里,把 props 解构到局部变量会失去响应。

追问 ​

为什么改一个没在模板里用到的属性,组件不更新?

渲染时没有读它,track 没有记录这个键。这是精确更新,不是 bug。

reactive 换掉整个对象会怎样?

替换绑定本身要靠 ref。let state = reactive(...) 再 state = reactive(...) 不会通知旧对象的订阅者,因为订阅在旧对象上。

和 React 的差别?

Vue 在读取时收集依赖,组件默认只跟用到的数据走。React 是状态变了就重新执行组件函数,再靠 memo 和编译器减少子树渲染。见 状态管理。

易错点 ​

  • 解构 reactive 对象会把当时的值拷出来,之后不再追踪。要保持联系可以用 toRefs。
  • 已经是代理的对象不要重复包一层去比较引用。
  • 响应式只存在于被读过的访问路径上。绕过代理去改原始对象(toRaw 之后写入)不会触发更新。

相关 ​