作者:啥也不会的码农
https://juejin.cn/post/7465330375663386650
背景
vue3最新版本目前已更新至3.5了,很多同学经过这几年的使用,相信对vue3的常用api都已经烂熟于心了。
但每每被问到源码时,还是虽表面强装镇定,实则内心慌的一批。。。就比如我们经常使用的reactive,很多同学最后就只会憋出一句:reactive的原理是proxy,然后……,就没有然后了
今天我就带着大家将reactive方法一撸到底。
总览
话不多说,直接上图,接下来将带着大家跟着这张图结合源码搞懂reactive的核心源码。

reactive
上面这张图分为上中下三部分,我们一部分一部分进行拆解,首先是最上面部分,这其实就是reactive函数的核心代码
假如我们有一个如下的example.js文件:
<script setup>
import { reactive,effect } from 'vue'
const obj = reactive({
name: '法外狂徒张三'
})
effect(() => {
document.getElementById('app').innerText = obj.name
})
</script>
当我们写下这段代码的时候,实际上是调用了vue中的reactive函数。我们可以在vue源码的packages\reactivity\src\reactive.ts中找到reactive函的实现:
可以看到reactive函数的实现非常简单,就仅仅返回了一个createReactiveObject方法执行后的结果。
我们看到,createReactiveObject函数最终是会执行new Proxy生成一个proxy实例,如果不了解Proxy的同学可以自行去MDN[1]中学习,然后将这个proxy代理对象和target以键值对的方式建立联系,后续当同一个target对象再次执行reactive函数时,直接从proxyMap中获取,最终返回这个proxy代理对象。
所以,整个reactive函数确实只完成了一件事,那就是生成并返回proxy代理对象,这也是大多数同学探索vue实现响应式原理的终止点。
baseHandlers
reactive函数生成的对象之所以能够实现响应式,是因为Proxy劫持了target对象的读取和写入操作,即Proxy的第二个参数:baseHandlers。 接下来,进入中间部分:
我们看看vue源码对baseHandlers的实现,进入packages\reactivity\src\baseHandlers.ts中我们可以看到以下代码(不重要的代码都被我删除了):
从createReactiveObject函数的参数,我们可以知道,Proxy构造函数中的第二个参数其实是MutableReactiveHandler实例,而MutableReactiveHandler继承了BaseReactiveHandler,因此该实例对象中会包含着一个get和set函数,这也是vue完成响应式原理的核心部分。
在get函数中,除了返回一个`Reflect.get`[2]的结果,还调用了一个track函数,track函数的实现在packages\reactivity\src\dep.ts中:

track函数的作用是收集依赖。它最终会构造一个类型为WeakMap[3]的targetMap,其键是我们传入的那个target对象,值是一个Map[4]类型的depsMap,depsMap中存放的才是target对象的key和dep的对应关系。而dep中存放的就是收集到的依赖。这么说起来有点绕,直接上图:

而在set中的trigger函数执行时,所有存储在dep中的依赖都会被挨个调用。
effect
我们可以看到,dep中的依赖是一个个的ReactiveEffect实例,而这个实例又是从何而来呢?这就要靠我们的effect函数了。

effect函数需要传递一个函数作为参数,这个函数被称之为副作用函数。
在effect函数中,会调用一个ReactiveEffect构造函数生成ReactiveEffect实例,这个实例会作为依赖被收集。实例中有一个run方法,并且在run方法执行时会调用effect函数传入的参数,即,副作用函数。从而触发proxy代理对象中的get行为,将这个ReactiveEffect实例作为依赖收集到dep中
总结
最后总结一下reactive函数的执行流程:首先,当我们调用reactive函数并传入一个target对象时,reactive内部会调用createReactiveObject函数生成并返回一个proxy代理对象。这个proxy代理对象中get方法会收集并以键值对的方式存储依赖,当改变对象的某个属性时,触发proxy的set函数,set函数中的trigger函数会从之前存储的对象中循环调用所有依赖。
阅读原文:原文链接
该文章在 2025/2/19 13:10:29 编辑过