【源码】Reactive 源码

前言

用了很长时间的 componsition-api 了,最近想看看源码,抱着单纯的学习心态先从 reactive 开始吧。

个人习惯:

  • 看代码要带着问题去看,不要盲目的去看
  • 问题就是这次看源码的主线,要围绕着主线去展开,过程中和主线没有多大关系的该忽略掉就忽略掉
  • 开源项目一般都封装的比较好,有可能一个函数中会引用多个文件中的函数,每次跳转的时候将跳转的目录记下来,避免跳着跳着就不知道跳哪去了
  • 进入到一个文件中先将函数都收起来,便于查看

前置准备

  • vue-next 从 github 上把项目 clone 下来。

  • 通过 yarn install 安装依赖。

  • package.jsondev 脚本增加 sourcemap 最终命令为: "dev": "node scripts/dev.js --sourcemap"

  • 因为 vue3 是通过 rollup 打包的,所以还需要安装 rollup

  • 执行 npm run dev 在 package/vue/ 目录下会生成一个 dist 文件夹。

  • 在 package/vue/examples/ 目录下新建 init.html 文件。

    <!DOCTYPE html>
    <html lang="en">
    <body><div id="app"><h1>hello</h1></div><script src="../dist/vue.global.js"></script><script>const { watch, watchEffect, createApp, reactive } = Vuedebuggerconst data = reactive({a: 1,b: 2,count: 0})</script>
    </body>
    </html>
    
  • 然后将这个文件以服务的形式跑起来进入 debug,通过断点可以进入到 reactive 函数。

数据响应式 Reactive

reactive 函数一开始就判断了如果传入的 target 如果是一个只读的对象则 return target

紧接着调用了 createReactiveObject 函数,先来看一下函数对应的参数:

  1. target 将要被代理的对象
  2. 是否只读
  3. baseHandlerscollectionHandlers 都是 proxyhandler,对应的实参分别是 mutableHandlersmutableCollectionHandlers,根据 target 类型来决定 proxyhandler
  4. reactiveMap 是当前文件中声明的一个常量,用于存储依赖,现在只需要它是一个 weakMap 类型数据就好

函数一开始对 target 做了几种情况的判断,针对不同情况做了对应的 return

主要关注点是这一段代码

const proxy = new Proxy(target,targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
)

这里会根据 target 的类型来选择对应的 handlertargetType 是调用 getTargetType 函数的返回值,targetTypeMap/Set/WeakMap/WeakSet 的时候会将 collectionHandlers 传入 Proxy,反之采用 baseHandlers

baseHandlers

baseHandlers 对应的 mutableHandlers

get

get 对应的 createGetter 的返回值

createGetter 接收两个参数 isReadonlyshallow,在 get 方法中先判断了三种特殊情况,针对每种不同情况 return 了不同的值,其中在这几个判断中用到的 shallowReadonlyMap/readonlyMap/shallowReactiveMap 它们的类型和 reactiveMap 的类型都一样都是 weakMap,只不过是对应不同的状态。

下边判断了是不是数组,如果是数组并且 isReadonlyfalsekeyincludes/indexOf/lastIndexOf/push/pop/shift/unshift/splice 其中的一个的话则执行

return Reflect.get(arrayInstrumentations, key, receiver)

也就是对上述那些方法进行了重写。这段代码是为了解决边缘情况

includes/indexOf/lastIndexOf 是为了避免产生以下情况

const obj = {}
const arr = reactive([obj])
arr.indexOf(obj) // -1 正常情况下应该返回的是 0

push/pop/shift/unshift/splice 是为了避免这些改变数组长度的方法在某些情况下进入死循环

再往下获取了 keytarget 中对应的值

const res = Reflect.get(target, key, receiver)

接着判断 keysymbol 的情况。如果 isReadonlyfalse 则调用 track 函数进行依赖收集。

track 这个函数往后放一下,看完这个 get 函数后再回头来看 track

接着又判断了 createGetter 传入的 shallowtrueresref 类型的这两种情况。

如果 res 还是一个对象并且 isReadonlyfalse 则递归调用 reactive 函数,反之调用 readonly 函数。

从调用 reactive 函数这里可以看出,vue3 中的响应式和 vue2 的差别不仅在 definePropertyproxy 上,在处理响应式的时机上也有变化,defineProperty 是一上来就将 target 上的所有属性都变成响应式的,但是 vue3 是在当你去读取这个 key 的时候,采取将 key 对应的 value 转换成响应式的。

接下来去看一下 track 函数,这个函数的作用是用来收集依赖的。

一开始是一个判断条件,接下来的这段代码是为了获取当前 key 对应的数据同时构造一个数据结构

let depsMap = targetMap.get(target)
if (!depsMap) {targetMap.set(target, (depsMap = new Map()))
}
let dep = depsMap.get(key)
if (!dep) {depsMap.set(key, (dep = new Set()))
}

构造出来的数据结构是这样的

targetMap = {target: {key: [dep,...] // dep, set 类型} // map 类型
} // weakMap 类型

后边将 activeEffect 添加到 dep 中。这里的 activeEffect 其实就是我们要收集的依赖。

后边又将 dep 添加到了 activeEffectdeps 中,这一块当时在看的时候并不明白,后来是查资料明白的,我们放到后边说。如果是开发环境还调用了 onTrack 函数,这个函数在文档中有说,用于调试侦听器的行为。

至此,track 函数就结束了。

遇到的问题
  • activeEffect.deps.push(dep) 是什么,为什么要这样做

    语义上来看是将当前 key 所对应的 dep push 到了 activeEffectdeps 中,但是为什么要这样做还有待思考。网上查了一番,这篇文章写的还是很不错的,对这个问题点也有一定的解析。

    文章内的总结: 这个操作是在为在 reactiveEffect 方法中提到的 cleanup 方法做准备,每次收集 activeEffect 之前,会先将 activeEffect 中的 deps 清空,然后再进行收集依赖。先清空再收集就是为了避免如果 activeEffect 中有在特定条件下才会触发的依赖收集,之前已经收集过了,但是这次不需要收集,所以会先把之前收集的清空掉,然后再针对当前这次需要收集的依赖进行收集,保证当前收集的依赖肯定是当前需要被收集的。这里又引申除了 reactiveEffect 方法,这个方法在 watchEffect 中会用到,本文不涉及,所以不展开说。

    set

set 对应的 createSetter 的返回值

createSetter 接收一个参数 shallow 用来表示是不是浅层对象,然后直接 returnset 函数。

首先判断了不是浅层对象则处理 oldValueref 类型,但 newValue 不是 ref 类型的情况,因为 ref 类型的数据已经是响应式的了,所以不需要再次通过 trigger 函数来再次触发依赖。

refreactive 差不多,都可以返回一个响应式的数据,但是 ref 需要通过 .value 的形式来获取值。

后边的代码就是判断 target 是不是数组,用 Reflect.set()value 添加到 target 中,后续又判断了 key 在不在 target 中,如果 key 不在 target 中则按 ADD 类型调用 trigger 函数触发依赖,反之以 SET 类型调用 trigger 函数。调用完之后将 Reflect.set() 的返回值 return 出去。

接下来看一下 trigger 函数

targetMap 中获取 target 对应的依赖,没有则 return

定义了 set 类型的 effectsadd 方法,add 方法主要是将传入的 deps 遍历,然后将每个 effect 添加到 effects 中,这样 effects 中就保存了所有待执行的 effect

下边就是根据传入的 type 来执行对应的 add 函数。

再往下就定义了 run 函数,它负责来执行 effect,如果是开发环境 effect 中有 onTrigger 方法的话会先执行 onTrigger 方法,这个方法和 track 函数中调用的 onTrack 函数是一个意思,文档中有说,用于调试侦听器的行为。如果 effect 中有调度器的话会选择用调度器来执行 effect 否则直接执行 effect

run 函数的定义完了就是遍历 effects,将 run 函数传入并执行。

以上就是 trigger 函数的所有了。

deleteProperty

这个方法就没什么好说的了,用 Reflect.deleteProperty() 执行删除,判断这个 keytarget 自身的则调用 trigger 触发依赖,然后 return Reflect.deleteProperty() 的返回值。

has

执行 Reflect.has(),在 key 是非 Symbol 类型的时候调用 track 函数收集依赖,然后 return Reflect.has() 的返回值。

ownKeys

调用 track 收集依赖,return Reflect.ownKeys(target)

collectionHandler

collectionHandlers 对应的 mutableCollectionHandlers

get

get 对应的 createInstrumentationGetter 的返回值

createInstrumentationGetter 函数定义了 isReadonlyshallow 两个参数,这里在调用的时候传入的都是 false

方法内也根据这两个参数去获取了对应的 handlers 定义为 instrumentations,后边判断了 key 的三种特殊情况,这里的 key 代表的是调用 get 方法时的 key,根据 key 的不同 return 对应的值。

最后判断 key 是不是 instrumentations 自身的属性,如果是则 Reflect.get() 传入的第一个值是 instrumentations,反之直接将 target,最后将 Reflect.get() 的返回值 return。

这个 collectionHandlers 不进行具体展开,主要还是围绕着我们的主题进行。

collectionHandlers 到此结束。

总结

  1. vue3 的响应式将 target 对象传入 proxy,然后利用 handlers,在执行 get/has/ownKeys 等获取值方法的时候进行依赖收集,在执行 set/deleteProperty 等更改值方法的时候进行触发依赖。

  2. vue3 对于 target 的某一个 keyvalue 值还是对象的时候,只有在读取到这个 key 的时候才将 value 进行响应式处理,而 vue2 的处理是初始化的时候直接将所有属性及属性值进行响应式处理。

    以上就是我关于 reactive 源码的阅读过程,在我最终读完之后去跑 demo 的时候发现进入到 track 函数的时候,在一开始那个判断的地方就被 return 出去了,并没有真正的收集依赖。这也就产生了我的另外一个问题:什么时候才会收集依赖呢。有了这个问题,可以继续把源码看下去了

参考链接

  • 源码系列:Vue3深入浅出(一)
  • Vue3 文档
  • Vue3最啰嗦的Reactivity数据响应式原理解析

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/news/911831.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/911831.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

银河麒麟 | ubuntu 安装国产达梦DM8数据库(安装+外网通+IDEA连接)

目录 官网下载安装 下载安装包 创建安装用户组dinstall 创建安装用户dmdba并指定组 创建DM8软件安装目录修改权限 检查、修改系统资源限制 解压.zip的压缩包 安装mount数据库 图形化安装 清除之前的挂载 开启Disql服务 修改dmdba的环境变量 检查状态 进入数据库 …

MySQL与Oracle视图:深入解析与全面对比

视图概念 视图在 MySQL 与Oracle中本质上是一种虚拟表&#xff0c;其数据并非实际存储&#xff0c;而是基于一个或多个基础表的查询结果动态生成。它像是对复杂查询的一种封装&#xff0c;极大地简化了数据的查询操作。例如&#xff0c;当我们需要频繁从多个关联表中获取特定数…

uniapp通过webview套h5时使用plus调取蓝牙/usb打印

安卓使用usb调取打印机 /*** 安卓usb调取打印机*param { string | bytes[] } html 传入的打印内容*传入一段文本或一个bytes数组* returns*/ export const printUsb (html) > {return new Promise((resolve, reject) > {if (!window.plus) return reject(new Error(&qu…

吃透 Golang 基础:基于共享变量的并发

文章目录 sync.Mutex 互斥锁sync.RWMutex 读写锁sync.Once 惰性初始化Goroutine 与线程动态栈Goroutine 调度GOMAXPROCSGoroutine 没有 ID 号 上一篇文章当中我们已经系统性地回顾了在 Go 当中基于 Goroutine 和 Channel 进行并发控制的方法&#xff0c;Goroutine 指的是 Golan…

智绅科技丨如何选择一家好的养老机构?

居家养老、社区养老和机构养老是我们在养老相关消息中常常听到的3个词。在地方文件中&#xff0c;居家养老和社区养老还经常被统称为居家社区养老或 社区居家养老。那么&#xff0c;这三者之间到底有什么不同呢&#xff1f; 居家养老服务涵盖生活照料、家政服务、康复护理、医…

【支持向量机】SVM线性支持向量机学习算法——软间隔最大化支持向量机

支特向量机(support vector machines, SVM)是一种二类分类模型。它的基本模型是定义在特征空间上的间隔最大的线性分类器。包含线性可分支持向量机、 线性支持向量机、非线性支持向量机。 当训练数据近似线性可分时&#xff0c;通过软间隔最大化学习线性分类器&#xff0c; 即为…

面试 — 预准备 — 面试前准备攻略

好记忆不如烂笔头&#xff0c;能记下点东西&#xff0c;就记下点&#xff0c;有时间拿出来看看&#xff0c;也会发觉不一样的感受. 只讲干货&#xff0c;不罗里吧嗦&#xff01; 作为一个软件从业者&#xff0c;在面试前的准备工作至关重要&#xff0c;能大幅提升你的求职成功…

Oracle停库shutdown长时间无反应

Oracle停库shutdown长时间无反应 现象:Oracle停库卡住,长时间没有反应。 SQL> shutdown immediate;注:此时切记不可Ctrl+C直接取消!切记不可Ctrl+C直接取消!切记不可Ctrl+C直接取消! 检查alert_SID.log日志看是哪些会话进程导致的: Shutting down instance (immed…

使用ZYNQ芯片和LVGL框架实现用户高刷新UI设计系列教程(第十八讲

列表部件基本上是一个采用垂直布局的矩形&#xff0c;可向其中添加按钮和文本。 部件包含&#xff1a; LV_PART_MAIN - 主要的属性&#xff0c;大部分是这个部件。 LV_PART_SCROLLBAR - 滚动条的属性。 &#xff08;1&#xff09; 添加文本 lv_obj_t * lv_list_add_text(lv_o…

Android Navigation 原理解析

1. nav_graph.xml 如何生成路由表 NavGraph 解析流程与原理 关键技术点&#xff1a; XML 解析&#xff1a; 使用 XmlResourceParser 解析 XML 文件 遍历所有节点&#xff08;<fragment>, <activity>, <navigation>等&#xff09; Destination 创建&#…

HarmonyOS 应用权限管控流程

HarmonyOS 应用权限管控流程详解 一、权限管控概述 HarmonyOS 通过多层次的安全机制保护用户数据和系统资源&#xff0c;其中应用权限管控是核心组成部分。系统通过以下机制实现权限管控&#xff1a; 应用沙箱&#xff1a;每个应用运行在独立沙箱中&#xff0c;通过TokenID识…

Python训练营-Day33

import torch torch.cudaimport torch# 检查CUDA是否可用 if torch.cuda.is_available():print("CUDA可用&#xff01;")# 获取可用的CUDA设备数量device_count torch.cuda.device_count()print(f"可用的CUDA设备数量: {device_count}")# 获取当前使用的C…

【STM32】中断优先级管理 NVIC

这篇文章是对 Cortex-M3 内核中断系统 和 STM32F1 系列 NVIC(嵌套向量中断控制器) 的解析说明。我将从结构清晰、层次分明的角度,对 NVIC 中断优先级分组的概念和 STM32F103 的实际情况做一个系统性的总结与叙述。 参考资料: STM32F1xx官方资料:《STM32中文参考手册V10》…

Angular2--高级特性(TODO)

1 基础 关于Angular的基础部分&#xff0c;几个核心部分和框架&#xff0c;在之前都写过了。Angular1--Hello-CSDN博客 Angular的几个核心部分和框架&#xff1a; 模板就是组件中的template&#xff0c;对应MVC的V。 组件类就是Component类&#xff0c;对应对应MVC的C。 服…

pikachu靶场通关笔记44 SSRF关卡02-file_get_content(三种方法渗透)

目录 一、SSRF 1、简介 2、原理 二、file_get_contents函数 1、功能 2、参数 3、返回值 4、file_get_contents与SSRF 三、渗透实战 1、基本探测 2、http协议 &#xff08;1&#xff09;访问upload-labs靶场 &#xff08;2&#xff09;访问yijuhua.txt 3、file协议…

Android 控件 - EditText 的 Hint(Hint 基本用法、Hint 进阶用法、单独设置 Hint 的大小)

一、EditText 的 Hint 1、基本介绍 在 Android 开发中&#xff0c;EditText 的 Hint 用于显示提示文本 提示文本当用户没有输入任何内容时显示&#xff0c;输入内容后自动消失 2、基本使用 &#xff08;1&#xff09;在 XML 布局文件中设置 在 XML 布局文件中设置 Hint …

PostgreSQL(知识片):索引关联度indexCorrelation

索引关联度的绝对值越大&#xff0c;说明这个索引数据越好。绝对值最大为1。 首先我们创建一个表&#xff1a;tbl_corr&#xff0c;包含列&#xff1a;col、col_asc、col_desc、col_rand、data&#xff0c;col_asc存储顺序数据&#xff0c;col_desc存储降序数据&#xff0c;col…

React纯函数和hooks原理

纯函数 JS 若满足其下条件 &#xff0c;被称为纯函数 1。确定的输入一定产生确定的输出 2 不产生副作用 另外redux中的reducer也要求是纯函数 Fiber 架构和hooks原理 useRef 在组件的整个声明周期内保持不变 用法&#xff1a;1绑定dom元素 或者 绑定一个类组件 因为函数式…

养老专业实训室虚拟仿真建设方案:助力人才培养与教育教学革新

随着我国老龄化程度加深&#xff0c;养老服务行业人才需求激增。养老专业实训室虚拟仿真建设方案凭借虚拟仿真技术&#xff0c;为养老专业教育教学带来革新&#xff0c;对人才培养意义重大。点击获取实训室建设方案 一、构建多元化虚拟场景&#xff0c;丰富实践教学内容 模拟居…

LangChain 提示词工程:语法结构详解与完整实战指南

LangChain 提示词工程&#xff1a;语法结构详解与完整实战指南 我将为您系统性地解析 LangChain 中各类提示模板的核心语法结构&#xff0c;通过清晰展示语法与对应代码示例&#xff0c;帮助您彻底掌握提示工程的实现方法。所有示例均围绕报幕词生成场景展开。 在这里插入图片…