手写 Vue 源码 === Effect 机制解析

目录

核心概念

响应式效果的实现

依赖收集的具体流程

为什么使用全局变量?

[嵌套 effect 的处理](#嵌套 effect 的处理)

总结


Vue3 的响应式系统核心在于跟踪依赖并在数据变化时触发更新。effect.ts文件实现了这一机制的核心部分,下面我们来梳理其中的关键思路。

核心概念

复制代码
// 创建一个响应式对象 effect
export function effect(fn, options: any = {}) {
  // 创建一个 effect 只要依赖的属性变化,就会重新执行
  const _effect = new ReactiveEffect(fn, () => {
    _effect.run();
  });
  // 执行
  _effect.run();
}

export let activeEffect; // 当前的 effect

响应式效果的实现

ReactiveEffect

类是整个响应式系统的核心,它负责:

  1. 存储用户传入的回调函数

  2. 提供执行机制

  3. 处理嵌套 effect 的情况

    class ReactiveEffect {
    /**
    * fn 是用户传入的回调函数「如果fn中依赖了响应式数据,当数据变化后,会重新调用scheduler -->run」
    * scheduler 是调度器
    */
    public active = true; //默认是响应式的
    constructor(public fn, public scheduler) {}
    run() {
    // 如果当前状态是停止的,执行后,啥都不做
    if (!this.active) {
    return this.fn();
    }

    复制代码
     /**
      *     effect(() => {
         console.log(state.name);
         effect(() => {
             console.log(state.name);
         })
         console.log(state.age);
     })
     执行前,保存当前的 activeEffect 可以理解为 「栈」执行完后,恢复上一次的 activeEffect
     */
     let lastEffect = activeEffect;
     try {
       activeEffect = this; // 当前的 effect 「依赖收集」
       return this.fn(); //依赖收集 「state.name ,state.age」
     } finally {
       activeEffect = lastEffect; // 执行完毕后 恢复上一次的 activeEffect
     }

    }
    }

activeEffect 是一个全局变量,它的核心作用是:标记当前正在执行的 effect。这是依赖收集的关键环节,让系统知道"当前访问的属性应该与哪个 effect 建立联系"

依赖收集的具体流程

让我们通过一个具体的例子来理解这个过程:

复制代码
const state = reactive({ name: 'zhang', age: 18 });

effect(() => {
  console.log(state.name); // 访问 name 属性
});

当这段代码执行时,发生了以下步骤:

  1. 调用 effect 函数,创建一个 ReactiveEffect 实例

  2. 调用 _effect.run()

  3. 在 run 方法中,将 activeEffect 设置为当前 effect 实例

  4. 执行用户传入的回调函数 fn()

  5. 在回调函数中访问 state.name

  6. 访问属性触发 Proxy 的 get 捕获器

  7. get 捕获器调用 track 函数

    get(target: any, key: any, receiver: any) {
    // ...
    track(target, key);
    return Reflect.get(target, key, receiver);
    }

track 函数检查 activeEffect 是否存在,如果存在,就建立依赖关系

复制代码
export function track(target, key) {
  if (activeEffect) {
    console.log(activeEffect, key);
    // 这里应该建立 target[key] 和 activeEffect 的映射关系
  }
}

为什么使用全局变量?

使用全局变量activeEffect的好处是:

  1. 简化 API 设计,用户不需要手动指定依赖关系
  2. 自动追踪运行时的依赖,只收集真正访问的属性
  3. 支持动态依赖收集,依赖可以根据条件变化

嵌套 effect 的处理

Vue3 巧妙地处理了嵌套 effect 的情况,通过维护当前活跃的 effect 和保存上一个 effect 的方式:

复制代码
effect(() => {
  console.log(state.name);  // 外层 effect 依赖 state.name
  
  effect(() => {
    console.log(state.name);  // 内层 effect 依赖 state.name
  })
  
  console.log(state.age);  // 外层 effect 还依赖 state.age
})

当处理嵌套 effect 时,代码会:

  • 保存当前的 activeEffect 到 lastEffect 变量
  • 将 activeEffect 设置为当前执行的 effect
  • 执行完毕后,恢复 activeEffect 为之前保存的值
  • 这就像一个栈结构,确保每个属性访问都能正确关联到当前正在执行的 effect。

总结

Vue3 响应式系统的核心在于:

  1. 通过 effect 函数创建响应式效
  2. 果使用 ReactiveEffect类管理回调函数和执行逻辑
  3. 通过 activeEffect 全局变量跟踪当前正在执行的 effect
  4. 在属性访问时进行依赖收集
  5. 在属性设置时触发相关 effect 重新执行

Vue3 响应式系统通过activeEffect 全局变量巧妙地解决了"谁在使用这个属性"的问题,实现了自动依赖收集。当访问响应式对象的属性时,系统知道是哪个 effect 正在运行,从而建立属性与 effect 之间的映射关系,为后续的响应式更新奠定基础。

相关推荐
仰望星空的凡人1 小时前
【JS逆向基础】并发爬虫
javascript·python
源码宝1 小时前
ERP进销存系统源码,SaaS模式多租户ERP管理系统,SpringBoot、Vue、UniAPP技术框架
vue.js·spring boot·uni-app·源代码管理·erp·erp系统·进销存
zhangguo20025 小时前
Vue之脚手架与组件化开发
前端·javascript·vue.js
苹果酱05678 小时前
【Azure Redis】Redis导入备份文件(RDB)失败的原因
java·vue.js·spring boot·mysql·课程设计
麻芝汤圆9 小时前
在 Sheel 中运行 Spark:开启高效数据处理之旅
大数据·前端·javascript·hadoop·分布式·ajax·spark
sunbyte10 小时前
Three.js + React 实战系列 - 项目展示区开发详解 Projects 组件(3D 模型 + 动效 + 状态切换)✨
javascript·react.js·3d
源码方舟10 小时前
【HTML5】显示-隐藏法 实现网页轮播图效果
前端·javascript·html·css3·html5
还是大剑师兰特11 小时前
vue源代码采用的设计模式分解
javascript·vue.js·设计模式
战族狼魂11 小时前
用html+js+css实现的战略小游戏
javascript·css·html
程序员小刚12 小时前
基于SpringBoot + Vue 的作业管理系统
vue.js·spring boot·后端