[vue3] Vue3源码阅读笔记 reactivity - baseHandlers

源码位置:https://github.com/vuejs/core/blob/main/packages/reactivity/src/baseHandlers.ts

baseHandler用于处理对象、数组类型的gettersetter

这个文件中主要有两个函数,和三个类。

arrayInstrucmentationshasOwnProperty这两个函数主要起到辅助作用;

3个类:

  • BaseReactiveHandler:负责处理getter
  • MutableReactiveHandlerReadonlyReactiveHandler:负责处理setter
graph LRA[ProxyHandler] --> B[BaseReactiveHandler]B --> C[MutableReactiveHandler] & D[ReadonlyReactiveHandler]

依赖

依赖比较零碎,大致分为以下几种:

  1. 用于判断数据类型;
  2. Vue内置的FlagType,用来标记对象或者操作的类型;
  3. 暂停与重置依赖追踪和任务调度的方法;
  4. 其它零碎的比如:
    • warning:在控制台输出警告信息;
    • makeMap:传入一个用,分隔的多个key组成的字符串,返回一个has函数用于检查后续传入的key是否存在于一开始传入的字符串中。

arrayInstrucmentations

arrayInstrumentations这个对象用于记录一些处理过的数组方法(拦截操作),通过createArrayInstrumentations构建后大概长这样:

{...'push': function(this, ...args){...},'indexOf': function(this, ...args){...},...
}

拦截这些方法的原因

  • ['includes', 'indexOf', 'lastIndexOf']:数组中可能包含响应式对象,这几个方法是需要比较数组元素的,直接比较可能会出错,因此需要拦截这些方法,在比较的过程中考虑使用toRaw转成原始对象进行比较。

  • ['push', 'pop', 'shift', 'unshift', 'splice']:这些方法会改变数组的长度length属性,从而触发与length属性相关的effect,而effect中如果又有这些方法,那么就会导致死循环。因此,这些方法需要被拦截做特殊处理,在执行这些方法的时候要暂停依赖的追踪和调度。

    相关的issue是:fix(reactivity): some mutation methods of Array cause infinite recursion by unbyte · Pull Request #2138 · vuejs/core (github.com)

    image-20240730004826476

源码与注释

const arrayInstrumentations = /*#__PURE__*/ createArrayInstrumentations()function createArrayInstrumentations() {const instrumentations: Record<string, Function> = {}// 给需要处理可能包含响应式值的数组方法增加拦截逻辑;(['includes', 'indexOf', 'lastIndexOf'] as const).forEach(key => {instrumentations[key] = function (this: unknown[], ...args: unknown[]) {// 将 this 转换为原始数组const arr = toRaw(this) as any// 追踪数组每个元素的 GET 操作for (let i = 0, l = this.length; i < l; i++) {track(arr, TrackOpTypes.GET, i + '')}// 先使用原始参数(可能是响应式的)运行原方法const res = arr[key](...args)// 如果结果是 -1 或 false,说明没有找到或不匹配,再次使用原始值运行一次if (res === -1 || res === false) {return arr[key](...args.map(toRaw))} else {return res}}})// 拦截会改变数组长度的方法,避免长度变化被追踪,从而防止出现无限循环的问题 (#2137);(['push', 'pop', 'shift', 'unshift', 'splice'] as const).forEach(key => {instrumentations[key] = function (this: unknown[], ...args: unknown[]) {// 暂停追踪pauseTracking()// 暂停调度pauseScheduling()// 使用原始数组运行原方法const res = (toRaw(this) as any)[key].apply(this, args)// 重置调度resetScheduling()// 重置追踪resetTracking()return res}})return instrumentations
}

hasOwnProperty

Vue对hasOwnProperty做了特殊处理,主要在于处理以下问题:

  • 确保需要检查的key只能是Symbol类型或string类型,如果是其它,则通过String()转为字符串;
  • 使用toRaw在原始对象上查询key是否存在;
  • 使用track追踪key

源码

function hasOwnProperty(this: object, key: unknown) {// #10455 hasOwnProperty may be called with non-string valuesif (!isSymbol(key)) key = String(key)const obj = toRaw(this)track(obj, TrackOpTypes.HAS, key)return obj.hasOwnProperty(key as string)
}

BaseReactiveHandler

这个类主要负责配置getter,当reactiveAPI包装的响应式对象的某个key被读取时,会触发这里的getter

  • 如果读取的key是内置的ReactiveFlags,返回相应的值;
  • 如果target是一个数组,那么需要应用上述arrayInstrucmentations记录的处理过的数组;
  • 如果keyhasOwnProperty,返回上述特殊处理过的hasOwnProperty
  • key记录依赖。
class BaseReactiveHandler implements ProxyHandler<Target> {constructor(protected readonly _isReadonly = false, // 是否只读protected readonly _isShallow = false, // 是否浅层响应式) {}get(target: Target, key: string | symbol, receiver: object) {const isReadonly = this._isReadonly,isShallow = this._isShallow// 处理 ReactiveFlags 特殊标志if (key === ReactiveFlags.IS_REACTIVE) {// 判断目标是否是响应式的return !isReadonly} else if (key === ReactiveFlags.IS_READONLY) {// 判断目标是否是只读的return isReadonly} else if (key === ReactiveFlags.IS_SHALLOW) {// 判断目标是否是浅层响应的return isShallow} else if (key === ReactiveFlags.RAW) {// 处理 RAW 标志if (receiver ===(isReadonly? isShallow? shallowReadonlyMap: readonlyMap: isShallow? shallowReactiveMap: reactiveMap).get(target) ||// receiver 不是响应式代理,但具有相同的原型// 这意味着 receiver 是响应式代理的用户代理Object.getPrototypeOf(target) === Object.getPrototypeOf(receiver)) {return target}// 提前返回 undefinedreturn}const targetIsArray = isArray(target)// 处理非只读的情况if (!isReadonly) {if (targetIsArray && hasOwn(arrayInstrumentations, key)) {// 对于数组的特殊方法,使用上述的方法拦截处理return Reflect.get(arrayInstrumentations, key, receiver)}if (key === 'hasOwnProperty') {// 特殊处理 hasOwnProperty 方法return hasOwnProperty}}// 默认的 Reflect.get 操作const res = Reflect.get(target, key, receiver)// 处理内置符号和非可追踪的键if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {return res}// 处理非只读情况,执行追踪操作if (!isReadonly) {track(target, TrackOpTypes.GET, key)}// 处理浅层响应情况if (isShallow) {return res}// 处理 Ref 类型的值if (isRef(res)) {// 如果是数组并且键是整数,则跳过 unwrapreturn targetIsArray && isIntegerKey(key) ? res : res.value}// 处理对象类型的值,将返回的值转化为代理对象if (isObject(res)) {// 将返回的值转换为代理对象,避免循环依赖return isReadonly ? readonly(res) : reactive(res)}return res}
}

MutableReactiveHandler

这个类实现了对setdeletePropertyhasownKeys的拦截

class MutableReactiveHandler extends BaseReactiveHandler {constructor(isShallow = false) {super(false, isShallow) // 调用父类构造函数,设置只读标志为 false}set(target: object,key: string | symbol,value: unknown,receiver: object,): boolean {let oldValue = (target as any)[key] // 获取目标对象中原有的值if (!this._isShallow) {// 如果不是浅层响应,进行深层处理const isOldValueReadonly = isReadonly(oldValue) // 判断原有值是否是只读的if (!isShallow(value) && !isReadonly(value)) {oldValue = toRaw(oldValue) // 获取原有值的原始对象value = toRaw(value) // 获取新值的原始对象}// 如果原有值是 ref 类型并且新值不是 ref 类型if (!isArray(target) && isRef(oldValue) && !isRef(value)) {if (isOldValueReadonly) {// 如果原有值是只读的,返回 falsereturn false} else {oldValue.value = value // 更新 ref 的值return true}}} else {// 在浅层模式中,直接设置对象,不考虑其是否为响应式}// 判断目标对象是否之前已经有这个键const hadKey =isArray(target) && isIntegerKey(key)? Number(key) < target.length: hasOwn(target, key)const result = Reflect.set(target, key, value, receiver) // 使用 Reflect 设置值// 判断target是否是实际被修改的对象if (target === toRaw(receiver)) {if (!hadKey) {// 如果之前没有这个键,触发 ADD 操作trigger(target, TriggerOpTypes.ADD, key, value)} else if (hasChanged(value, oldValue)) {// 如果键已经存在且新值不同,触发 SET 操作trigger(target, TriggerOpTypes.SET, key, value, oldValue)}}return result}deleteProperty(target: object, key: string | symbol): boolean {const hadKey = hasOwn(target, key) // 判断目标对象是否有这个键const oldValue = (target as any)[key] // 获取原有值const result = Reflect.deleteProperty(target, key) // 使用 Reflect 删除属性if (result && hadKey) {// 如果删除成功且目标对象之前有这个键,触发 DELETE 操作trigger(target, TriggerOpTypes.DELETE, key, undefined, oldValue)}return result}has(target: object, key: string | symbol): boolean {const result = Reflect.has(target, key) // 使用 Reflect 检查是否存在该键if (!isSymbol(key) || !builtInSymbols.has(key)) {// 如果键不是内置符号,追踪 HAS 操作track(target, TrackOpTypes.HAS, key)}return result}ownKeys(target: object): (string | symbol)[] {// 追踪 ITERATE 操作,用于获取对象的所有键track(target,TrackOpTypes.ITERATE,isArray(target) ? 'length' : ITERATE_KEY,)return Reflect.ownKeys(target) // 使用 Reflect 获取对象的所有键}
}

解析set里的trigger时机:

// 判断target是否是实际被修改的对象
if (target === toRaw(receiver)) {if (!hadKey) {// 如果之前没有这个键,触发 ADD 操作trigger(target, TriggerOpTypes.ADD, key, value)} else if (hasChanged(value, oldValue)) {// 如果键已经存在且新值不同,触发 SET 操作trigger(target, TriggerOpTypes.SET, key, value, oldValue)}
}

如果target !== toRaw(receiver)说明此时不是在对象上修改它本身的属性,而是通过原型链上的其它对象。这种情况不会触发更新。

ReadonlyReactiveHandler

这个类比较简单,主要是拦截setdeleteProperty这两个会改变对象的操作。

开发模式下会在控制台输出警告。

注意setdeleteProperty操作会返回true,这是为了符合Proxy规范:即使某些操作被拦截并不实际改变对象的状态,仍然需要返回一个布尔值以指示操作的成功或失败。

class ReadonlyReactiveHandler extends BaseReactiveHandler {constructor(isShallow = false) {super(true, isShallow) // 调用父类构造函数,将 isReadonly 设置为 true,表示对象是只读的}set(target: object, key: string | symbol, value: unknown): boolean {if (__DEV__) {warn(`Set operation on key "${String(key)}" failed: target is readonly.`,target,)}return true // 返回 true,表示设置操作被忽略}deleteProperty(target: object, key: string | symbol): boolean {if (__DEV__) {warn(`Delete operation on key "${String(key)}" failed: target is readonly.`,target,)}return true // 返回 true,表示删除操作被忽略}
}

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

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

相关文章

Go语言结构体(struct)面向对象编程进阶篇(封装,继承和多态)

作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任。 目录一.Go语言的封装(encapsulation)实现1.什么是封装(encapsulation)2.封装(encapsulation)的好处3.golang如何实现封装(encapsulation)4.代码实现4.1 代码组织结构4.2 创建go.mod文件4.3 dongman.go4.4 mai…

解决历理 如何用cmd运行小键盘

cmd不可以直接运行键盘上的按键,但是可以打开电脑上的软键盘来对应键盘键。 打开软键盘来运行键盘上的按键步骤如下: 1、点击Windows徽标键和R键,在出现的窗口中输入cmd; 2、点击确定,弹出命令提示符窗口; 3、在命令提示符窗口输入osk,点击键盘上的Enter,屏幕上就会出现…

ARGOCD用户管理

1.创建用户alice kubectl apply -f argocd-cm.yamlapiVersion: v1 kind: ConfigMap metadata:name: argocd-cmnamespace: argocdlabels:app.kubernetes.io/name: argocd-cmapp.kubernetes.io/part-of: argocd data:# add an additional local user with apiKey and login capab…

全网最适合入门的面向对象编程教程:27 类和对象的Python实现-Python中异常层级与自定义异常类的实现

本文主要介绍了在使用Python进行面向对象编程时,异常的层级和如何使用继承关系完成自定义自己项目中异常类,并以传感器数据采集为例进行讲解。全网最适合入门的面向对象编程教程:27 类和对象的 Python 实现-Python 中异常层级与自定义异常类的实现摘要: 本文主要介绍了在使…

初识WPF

1.新建wpf程序,app.xaml文件说明: 2.Attribute特征:为了表示同类标签中的某个标签阈值不同,可以给它的特征赋值: 3.命名空间的引用:使用xmlns特征来定义名称空间:语法格式为:xmlns[:可选的映射前缀]=“命名空间”,如果没有写映射前缀,说明是默认的命名空间: 4.x:Cl…

认知觉醒-1-大脑的构造

最近重新开始审视自己的生活和规划,无意间在微信读书上看到了《认知觉醒》这本书评价挺不错的,所以通过听书和看书的方式进行了学习,感觉对自己的现状很有启发,简单记录一下自己的读书心得。 大脑的构造导致了你就是不爱学习 本书提到,人类的大脑是由三部分构成的,可以分…

前端断点及使用

原文链接:https://www.cnblogs.com/sunny3158/p/17797552.html 一、打断点的方式1.找到源码位置,添加断点(1)寻找源码位置①如果是正常html页面,那么源码一般是在对应域名下面。如果是webpack处理的页面,并且开启了源码映射,源码就是在webpack://下面。可通过快捷键ctrl…

emojiCTF2024

emojiCTF2024emojiCTF2024 WEB http 题目: ​​ 思路:修改 UA 头为 EMOJI_CTF_User_Agent_v1.0:User-Agent: EMOJI_CTF_User_Agent_v1.0​ 修改 http 方法,试了一下,修改成 PUT,可以 添加一个自定义头部,EMOJI-CTF-Auth: Passw0rd!​ 抓包修改就行,成功后在路径上加个 …

2024 年巴黎奥运会 All In One

2024 年巴黎奥运会 All In One2024 年巴黎奥运会 All In One https://olympics.com/zh/paris-2024 奖牌统计 - 2024年巴黎奥运会奖牌榜 https://olympics.com/zh/paris-2024/medals https://olympics.com/zh/paris-2024/medals/medallistsmewatch https://www.mewatch.sg/paris…

7.29.01

fish = 1while True:temp, YES = fish, Truefor _ in range(5):if (temp-1)%5 == 0:temp = (temp-1)//5 * 4else: YES = Falseif YES:print(fish)breakelse: fish += 13121

低空经济

思维导图:低空经济

痞子衡嵌入式:MCUXpresso IDE下在线联合调试i.MXRT1170双核工程的三种方法

大家好,我是痞子衡,是正经搞技术的痞子。今天痞子衡给大家分享的是MCUXpresso IDE下在线联合调试i.MXRT1170双核工程的三种方法。两年前痞子衡写过一篇《i.MXRT1170下在线联合调试双核工程的三种方法(IAR篇)》,那篇文章详细介绍了 IAR 下调试 RT1170 双核工程的几种方法。现…