Skip to content

本文由 简悦 SimpRead 转码, 原文地址 mp.weixin.qq.com

背景

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,因此该实例对象中会包含着一个getset函数,这也是 vue 完成响应式原理的核心部分。

get函数中,除了返回一个 Reflect.get[2] 的结果,还调用了一个track函数,track函数的实现在packages\reactivity\src\dep.ts中:

track函数的作用是收集依赖。它最终会构造一个类型为 WeakMap[3] 的targetMap, 其键是我们传入的那个target对象,值是一个 Map[4] 类型的depsMapdepsMap中存放的才是target对象keydep的对应关系。而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 函数会从之前存储的对象中循环调用所有依赖。

作者:啥也不会的码农

https://juejin.cn/post/7465330375663386650