手写 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 之间的映射关系,为后续的响应式更新奠定基础。

相关推荐
Sheldon一蓑烟雨任平生3 小时前
Vue3 插件(可选独立模块复用)
vue.js·vue3·插件·vue3 插件·可选独立模块·插件使用方式·插件中的依赖注入
鱼与宇4 小时前
苍穹外卖-VUE
前端·javascript·vue.js
用户47949283569154 小时前
Safari 中文输入法的诡异 Bug:为什么输入 @ 会变成 @@? ## 开头 做 @ 提及功能的时候,测试同学用 Safari 测出了个奇怪的问题
前端·javascript·浏览器
裴嘉靖5 小时前
Vue 生成 PDF 完整教程
前端·vue.js·pdf
毕设小屋vx ylw2824265 小时前
Java开发、Java Web应用、前端技术及Vue项目
java·前端·vue.js
冴羽6 小时前
今日苹果 App Store 前端源码泄露,赶紧 fork 一份看看
前端·javascript·typescript
蒜香拿铁6 小时前
Angular【router路由】
前端·javascript·angular.js
时间的情敌6 小时前
Vite 大型项目优化方案
vue.js
西洼工作室6 小时前
高效管理搜索历史:Vue持久化实践
前端·javascript·vue.js
樱花开了几轉6 小时前
element ui下拉框踩坑
开发语言·javascript·ui