当前位置: 首页 > news >正文

【Vue3源码解析】响应式原理

源码环境搭建

【Vue3源码解析】应用实例创建及页面渲染-CSDN博客

写文章时的Vue 版本:

"version": "3.5.13",

针对单个包进行开发环境打包、测试。

pnpm run dev reactivity

image-20250215180341644

image-20250215180401097

reactive 创建响应式对象

packages/reactivity/src/reactive.ts

export function reactive(target: object) {// if trying to observe a readonly proxy, return the readonly version.if (isReadonly(target)) {return target}/*** 通过调用 createReactiveObject 函数,创建一个响应式对象,并返回该对象。* 第一个参数是 目标对象* 第二个参数用来判断该对象是否是只读的* mutableHandlers 是一个对象,包含了响应式对象的一些操作方法,如get、set等。* mutableCollectionHandlers 是一个对象,包含了响应式集合对象的一些操作方法,如get、set等。* reactiveMap 是一个全局 WeakMap 对象,用于缓存 target 到 响应式对象的映射关系。*/return createReactiveObject(target,false,mutableHandlers,mutableCollectionHandlers,reactiveMap,)
}
function createReactiveObject(target: Target,isReadonly: boolean,baseHandlers: ProxyHandler<any>,collectionHandlers: ProxyHandler<any>,proxyMap: WeakMap<Target, any>,
) {if (!isObject(target)) {if (__DEV__) {warn(`value cannot be made ${isReadonly ? 'readonly' : 'reactive'}: ${String(target,)}`,)}return target}// target is already a Proxy, return it.// exception: calling readonly() on a reactive object// 如果已经是 Proxy 代理对象,则直接返回if (target[ReactiveFlags.RAW] &&!(isReadonly && target[ReactiveFlags.IS_REACTIVE])) {return target}// target already has corresponding Proxy// 如果 ProxyMap 中有 target 对应的 proxy 对象 则直接返回const existingProxy = proxyMap.get(target)if (existingProxy) {return existingProxy}// only specific value types can be observed.// 类型无效 则直接返回 targetconst targetType = getTargetType(target)if (targetType === TargetType.INVALID) {return target}// 创建 target 对应的 Proxy 代理对象 并传入 baseHandlersconst proxy = new Proxy(target,// COLLECTION => Map/Set/WeakMap/WeakSettargetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers,)proxyMap.set(target, proxy)return proxy
}

effect 和它的函数参数fn的作用

packages/reactivity/src/effect.ts

/*** 创建一个响应式副作用函数** 此函数用于追踪函数内的响应式数据访问和修改,以便在数据变化时自动重新执行该函数* 它是响应式系统的核心部分,使得开发者可以轻松创建响应式的应用程序** @param fn 要追踪的副作用函数,它会接收一个用于触发副作用的函数作为参数* @param options 可选的副作用选项,允许开发者自定义副作用的行为,例如是否应该停止追踪等* @returns 返回一个绑定了副作用函数的runner函数,用于手动触发副作用*/
export function effect<T = any>(fn: () => T,// 可以由用户自定义,用于定义副作用effect的行为,例如shouldTrack、onTrack、onTrigger等// 这样可以覆盖ReactiveEffect的默认scheduler而执行自己的scheduleroptions?: ReactiveEffectOptions,
): ReactiveEffectRunner<T> {// 检查传入的函数是否已经是一个ReactiveEffect的副作用函数,如果是,则使用其内部的副作用函数if ((fn as ReactiveEffectRunner).effect instanceof ReactiveEffect) {fn = (fn as ReactiveEffectRunner).effect.fn}// 调用一次effect函数,根据传入的fn创建一个新的ReactiveEffect实例对象:_effect// 一个fn 对应一个 _effect对象// fn 同时成为_effect 对象的fn属性(形成闭包)/*** effect(fn)* fn.effect => ReactiveEffect对象* ReactiveEffect对象.fn => fn*/// 创建一个新的ReactiveEffect实例/*** 当内部执行scheduler的时候,它会回头调用effect的run,·而run方法内部会调用fn* 意味着:scheduler()=>run()=>fn()* 如何执行:那么之后我们如果想要重新执行fn函数,·只需要执行scheduler就可以了*/const e = new ReactiveEffect(fn)// 如果提供了用户自定义的配置,则将配置合并到副作用实例中if (options) {extend(e, options)}// 尝试运行副作用函数,如果在执行过程中抛出错误,则停止追踪并重新抛出错误try {e.run()} catch (err) {e.stop()throw err}// 创建并返回一个副作用函数的runner,它绑定了当前的副作用实例const runner = e.run.bind(e) as ReactiveEffectRunnerrunner.effect = e// 返回副作用函数的runner,允许手动触发副作用 e.run()=> fn()return runner
}
/*** 执行当前的副作用函数** 此函数负责执行副作用操作,并在执行前后处理一些清理和准备依赖项的工作* 它还管理副作用的执行状态,确保在执行过程中不会被中断,并在完成后清理依赖项** @returns 返回副作用函数的执行结果*/
run(): T {// TODO cleanupEffect// 检查副作用是否处于非激活状态,如果是,则直接执行副作用函数if (!(this.flags & EffectFlags.ACTIVE)) {// stopped during cleanupreturn this.fn()}// 设置当前副作用为正在运行状态this.flags |= EffectFlags.RUNNING// 执行清理上一次执行的副作用cleanupEffect(this)// 准备依赖项收集prepareDeps(this)// 保存当前正在执行的副作用和是否应该跟踪的全局状态const prevEffect = activeSubconst prevShouldTrack = shouldTrack// 设置当前执行的副作用和全局跟踪状态activeSub = thisshouldTrack = truetry {// 执行副作用函数return this.fn()} finally {// 在开发环境下,确保当前执行的副作用被正确恢复if (__DEV__ && activeSub !== this) {warn('Active effect was not restored correctly - ' +'this is likely a Vue internal bug.',)}// 清理当前执行的副作用的依赖项cleanupDeps(this)// 恢复之前保存的正在执行的副作用和是否应该跟踪的全局状态activeSub = prevEffectshouldTrack = prevShouldTrack// 清除当前副作用的正在运行状态this.flags &= ~EffectFlags.RUNNING}
}

执行fn后响应式变量依赖的收集和触发

Vue3.4 版本之前最后的一层,也就是 nameDepMap 这一层是 Set 结构,但是从 3.4 版本开始,变成了 Map ,用来记录 track 的 id,然而Vue3.5对该部分又进行了重构,不再记录id,只能说学的速度永远跟不上技术更新的速度~

image-20250215181003656

packages/reactivity/src/baseHandlers.ts

/*** BaseReactiveHandler 类实现了 ProxyHandler 接口,用于处理响应式对象的代理操作。* 它主要负责拦截和处理对响应式对象的访问和操作,根据是否只读和是否浅层次来决定如何处理这些操作。** @param _isReadonly 是否只读,如果为 true,则该响应式对象不可被修改。* @param _isShallow 是否浅层次,如果为 true,则仅对外层属性进行响应式处理,不递归处理内部属性。*/
class BaseReactiveHandler implements ProxyHandler<Target> {constructor(protected readonly _isReadonly = false,protected readonly _isShallow = false,) {}/*** 拦截对目标对象属性的获取操作。* 此方法负责处理各种情况下的属性获取,包括处理特殊的 ReactiveFlags、数组的特殊情况、* 属性的追踪、以及返回值的处理等。** @param target 目标对象,即被代理的响应式对象。* @param key 要访问的属性名。* @param receiver 代理对象或继承链上的其他对象。* @returns 属性值或处理后的值。*/get(target: Target, key: string | symbol, receiver: object): any {// 处理特殊的 ReactiveFlags 属性if (key === ReactiveFlags.SKIP) return target[ReactiveFlags.SKIP]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) {if (receiver ===(isReadonly? isShallow? shallowReadonlyMap: readonlyMap: isShallow? shallowReactiveMap: reactiveMap).get(target) ||// receiver is not the reactive proxy, but has the same prototype// this means the receiver is a user proxy of the reactive proxyObject.getPrototypeOf(target) === Object.getPrototypeOf(receiver)) {return target}// early return undefinedreturn}const targetIsArray = isArray(target)// 处理非只读情况下的特殊属性和方法if (!isReadonly) {let fn: Function | undefinedif (targetIsArray && (fn = arrayInstrumentations[key])) {return fn}if (key === 'hasOwnProperty') {return hasOwnProperty}}// 获取属性值,考虑内置符号和非跟踪键的情况const res = Reflect.get(target,key,// if this is a proxy wrapping a ref, return methods using the raw ref// as receiver so that we don't have to call `toRaw` on the ref in all// its class methodsisRef(target) ? target : receiver,)if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {return res}// 在非只读情况下,跟踪属性的访问if (!isReadonly) {// 依赖追踪track(target, TrackOpTypes.GET, key)}// 根据是否浅层次和返回值的类型,决定是否需要进一步处理返回值if (isShallow) {return res}if (isRef(res)) {// ref unwrapping - skip unwrap for Array + integer key.return targetIsArray && isIntegerKey(key) ? res : res.value}if (isObject(res)) {// Convert returned value into a proxy as well. we do the isObject check// here to avoid invalid value warning. Also need to lazy access readonly// and reactive here to avoid circular dependency.return isReadonly ? readonly(res) : reactive(res)}return res}
}
class MutableReactiveHandler extends BaseReactiveHandler {constructor(isShallow = false) {super(false, isShallow)}/*** 设置一个响应式对象的属性。** 该方法重写了 `Proxy` 对象的默认 `set` 行为,实现了自定义的属性设置逻辑。* 它处理了各种情况,例如浅层响应式、只读属性和 ref 解包,以确保响应式系统的正确行为。** @param target - 被代理的原始对象。* @param key - 要设置的属性的名称或符号。* @param value - 属性的新值。* @param receiver - 代理对象本身。* @returns 返回设置属性的结果。*/set(target: Record<string | symbol, unknown>,key: string | symbol,value: unknown,receiver: object,): boolean {let oldValue = target[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) {return false} else {// 否则,直接设置旧值 ref 的值。oldValue.value = valuereturn true}}} else {// 在浅层模式下,对象按原样设置,无论是否是响应式的。}// 确定键是否已经存在。const hadKey =isArray(target) && isIntegerKey(key)? Number(key) < target.length: hasOwn(target, key)// 使用 Reflect.set 尝试设置属性。const result = Reflect.set(target,key,value,isRef(target) ? target : receiver,)// 如果目标是原始对象本身,根据属性是新增还是更新触发相应的副作用。if (target === toRaw(receiver)) {if (!hadKey) {// 触发新增属性的副作用。trigger(target, TriggerOpTypes.ADD, key, value)} else if (hasChanged(value, oldValue)) {// 触发更新属性的副作用。trigger(target, TriggerOpTypes.SET, key, value, oldValue)}}return result
}deleteProperty(target: Record<string | symbol, unknown>,key: string | symbol,): boolean {const hadKey = hasOwn(target, key)const oldValue = target[key]const result = Reflect.deleteProperty(target, key)if (result && hadKey) {trigger(target, TriggerOpTypes.DELETE, key, undefined, oldValue)}return result}has(target: Record<string | symbol, unknown>, key: string | symbol): boolean {const result = Reflect.has(target, key)if (!isSymbol(key) || !builtInSymbols.has(key)) {track(target, TrackOpTypes.HAS, key)}return result}ownKeys(target: Record<string | symbol, unknown>): (string | symbol)[] {track(target,TrackOpTypes.ITERATE,isArray(target) ? 'length' : ITERATE_KEY,)return Reflect.ownKeys(target)}
}

packages/reactivity/src/dep.ts

/*** 跟踪对响应式属性的访问。** 该函数会检查当前正在运行的效果(effect),并将其记录为依赖项(dep),* 这些依赖项记录了所有依赖于该响应式属性的效果。** @param target - 包含响应式属性的对象。* @param type - 定义对响应式属性的访问类型。* @param key - 要跟踪的响应式属性的标识符。*/
export function track(target: object, type: TrackOpTypes, key: unknown): void {// 仅在启用了跟踪且存在活动效果时进行跟踪。activeSub在3.4版本及之前为activeEffectif (shouldTrack && activeSub) {// 获取目标对象的依赖映射,如果不存在则创建一个新的映射。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 Dep()))dep.map = depsMapdep.key = key}// 在开发模式下记录依赖项,并包含详细信息。if (__DEV__) {dep.track({target,type,key,})} else {dep.track()}}
}
/*** 查找与目标对象(或特定属性)关联的所有依赖项,并触发存储在其中的效果。** @param target - 响应式对象。* @param type - 定义需要触发效果的操作类型。* @param key - 可用于指定目标对象中的特定响应式属性。* @param newValue - 新值,用于某些操作类型(如 SET)。* @param oldValue - 旧值,用于某些操作类型(如 SET)。* @param oldTarget - 旧的目标对象,用于某些操作类型(如 CLEAR)。*/
export function trigger(target: object,type: TriggerOpTypes,key?: unknown,newValue?: unknown,oldValue?: unknown,oldTarget?: Map<unknown, unknown> | Set<unknown>,
): void {const depsMap = targetMap.get(target)if (!depsMap) {// 从未被追踪过globalVersion++return}const run = (dep: Dep | undefined) => {if (dep) {if (__DEV__) {dep.trigger({target,type,key,newValue,oldValue,oldTarget,})} else {dep.trigger()}}}startBatch()if (type === TriggerOpTypes.CLEAR) {// 集合被清除// 触发目标对象的所有效果depsMap.forEach(run)} else {const targetIsArray = isArray(target)const isArrayIndex = targetIsArray && isIntegerKey(key)if (targetIsArray && key === 'length') {const newLength = Number(newValue)depsMap.forEach((dep, key) => {if (key === 'length' ||key === ARRAY_ITERATE_KEY ||(!isSymbol(key) && key >= newLength)) {run(dep)}})} else {// 调度 SET | ADD | DELETE 的运行if (key !== void 0 || depsMap.has(void 0)) {run(depsMap.get(key))}// 调度 ARRAY_ITERATE 对于任何数字键的变化(长度已在上方处理)if (isArrayIndex) {run(depsMap.get(ARRAY_ITERATE_KEY))}// 也对 ADD | DELETE | Map.SET 运行迭代键switch (type) {case TriggerOpTypes.ADD:if (!targetIsArray) {run(depsMap.get(ITERATE_KEY))if (isMap(target)) {run(depsMap.get(MAP_KEY_ITERATE_KEY))}} else if (isArrayIndex) {// 新索引添加到数组 -> 长度变化run(depsMap.get('length'))}breakcase TriggerOpTypes.DELETE:if (!targetIsArray) {run(depsMap.get(ITERATE_KEY))if (isMap(target)) {run(depsMap.get(MAP_KEY_ITERATE_KEY))}}breakcase TriggerOpTypes.SET:if (isMap(target)) {run(depsMap.get(ITERATE_KEY))}break}}}endBatch()
}
ITERATE_KEY))}} else if (isArrayIndex) {// 新索引添加到数组 -> 长度变化run(depsMap.get('length'))}breakcase TriggerOpTypes.DELETE:if (!targetIsArray) {run(depsMap.get(ITERATE_KEY))if (isMap(target)) {run(depsMap.get(MAP_KEY_ITERATE_KEY))}}breakcase TriggerOpTypes.SET:if (isMap(target)) {run(depsMap.get(ITERATE_KEY))}break}}}endBatch()
}
http://www.lryc.cn/news/537640.html

相关文章:

  • servlet中的ServletContext
  • 第1825天 | 我的创作纪念日:缘起、成长经历、大方向
  • 如何在 Mac 上解决 Qt Creator 安装后应用程序无法找到的问题
  • Java 设计模式之迭代器模式
  • 登录演示和功能拆解
  • DeepSeek深度求索API多线程批量写原创文章软件-ai痕迹极低
  • Redis进阶使用
  • Python常见面试题的详解6
  • Linux基础之文件权限的八进制表示法
  • 数据结构与算法面试专题——堆排序
  • 《On Java进阶卷》阅读笔记(五)
  • 《代码随想录》刷题笔记——回溯篇【java实现】
  • 数值积分:通过复合梯形法计算
  • AcWing——3624. 三值字符串
  • 【JavaEE进阶】验证码案例
  • Uniapp 短视频去水印解析工具开发实现
  • 计算机网络-八股-学习摘要
  • 编程速递-庆祝Delphi诞生30周年!
  • 每天五分钟深度学习框架pytorch:搭建谷歌的Inception网络模块
  • 性能测试流程、主流性能工具
  • DeepSeek4j 已开源,支持思维链,自定义参数,Spring Boot Starter 轻松集成,快速入门!建议收藏
  • 无耳科技 Solon v3.0.8 发布,Java 企业级应用开发框架
  • 【吾爱出品】针对红警之类老游戏适用WIN10和11的补丁cnc-ddraw7.1汉化版
  • 使用 playwright 自定义 js 下载的路径和文件名
  • Kafka分区管理大师指南:扩容、均衡、迁移与限流全解析
  • 3.从零开始学会Vue--{{生命周期,工程化,组件化}}
  • Python--网络编程
  • 【java】方法的基本内存原理(栈和堆)
  • SQLMesh 系列教程4- 详解模型特点及模型类型
  • SpringBoot(接受参数相关注解)