【Vue3源码学习】— CH2.5 reactiveEffect.ts:Vue 3响应式系统的核心

reactiveEffect.ts:Vue 3响应式系统的核心

  • 1. 什么是 reactiveEffect?
  • 2. 核心机制
    • 2.1 依赖收集(Track)
    • 2.2 触发更新(Trigger)
    • 2.3 效果范围(effectScope)
  • 3. 源码解析 —— track
    • 3.1 track
    • 3.2 参数 target 究竟是什么
      • 3.2.1 target是什么
      • 3.2.2 怎么理解target
      • 3.2.3 小结
  • 4. 源码解析 —— trigger
  • 5. 应用场景
    • 5.1 组件渲染
    • 5.2 计算属性
    • 5.3 观察者(watch)
  • 6. 小结

Vue 3的响应式系统是基于Proxy和Reflect API构建的,其中reactiveEffect扮演了核心角色,实现了数据的响应式变化追踪和视图的自动更新。本章节我们将深入探讨reactiveEffect的工作原理。

1. 什么是 reactiveEffect?

在Vue中,每当我们操作响应式数据时,都会触发reactiveEffect来跟踪这些操作。无论是计算属性、watch监听器,还是组件的渲染函数,都被视为副作用函数,并被reactiveEffect所管理。

2. 核心机制

2.1 依赖收集(Track)

track函数负责在副作用函数首次执行时收集所有被访问的响应式数据的依赖。这意味着,当一个数据项被读取时,所有依赖于这个数据的副作用函数都会被记录下来。

2.2 触发更新(Trigger)

当响应式数据变化时,trigger函数会找到所有依赖于这个数据的副作用函数,并重新执行它们,从而实现数据到视图的自动更新。

2.3 效果范围(effectScope)

effectScope是Vue 3引入的新概念,它允许开发者组织和管理多个reactiveEffect。这对于在组件卸载或需要清理副作用时非常有用,避免内存泄漏。

3. 源码解析 —— track

3.1 track

/**
 * track函数负责追踪一个响应式对象的属性访问操作
 *
 * 它的主要作用是确定当前哪个副作用函数(effect)正在运行
 * 并将这个副作用函数记录为该响应式属性的依赖
 *
 * @param target - 被访问属性所属的响应式对象.
 * @param type - 访问类型,由TrackOpTypes枚举定义,比如读取、写入.
 * @param key - 被访问的响应式属性的标识符.
 */
export function track(target: object, type: TrackOpTypes, key: unknown) {
  /**
    * 1.条件判断: 首先检查是否应该进行依赖收集,这由shouldTrack和activeEffect共同决定。
    * shouldTrack是一个标志,表明是否应该收集依赖;
    * activeEffect指的是当前正在执行的副作用函数。
    */
  if (shouldTrack && activeEffect) {
    /**
     * 2.获取依赖映射: 使用targetMap(一个全局WeakMap,在上一章节详细介绍过)尝试获取当前对象的依赖映射depsMap。
     * 如果不存在,就为这个对象创建一个新的Map并设置到targetMap中。
     */
    let depsMap = targetMap.get(target)
    if (!depsMap) {
      targetMap.set(target, (depsMap = new Map()))
    }
    /**
     * 3. 获取依赖集合: 尝试从depsMap中获取对应属性key的依赖集合dep。
     * 如果这个属性还没有依赖集合,就创建一个新的依赖集合,并设置一个清理函数,当依赖集合为空时从depsMap中移除这个属性的记录
     */
    let dep = depsMap.get(key)
    if (!dep) {
      depsMap.set(key, (dep = createDep(() => depsMap!.delete(key))))
    }
    /**
     * 4. 追踪效果: 最后,使用trackEffect函数将当前的activeEffect(副作用函数)添加到这个属性的依赖集合中。
     * 如果是开发模式(__DEV__为true),还会传递额外的调试信息,包括被访问的对象、访问类型和属性标识符。
     */
    trackEffect(
      activeEffect,
      dep,
      __DEV__
        ? {
            target,
            type,
            key,
          }
        : void 0,
    )
  }
}

3.2 参数 target 究竟是什么

在 baseHandlers.ts 里的get函数里,我们说target是原始对象,而在track函数里,我们说target是被访问属性所属的响应式对象,但是它们又是同一个值,这让人很困惑。

3.2.1 target是什么

看一下proxy里关于捕获器的实例:

const target ={
    foo: 'bar'
}
const handler={
    get(trapTarget, property, receiver) {
        console.log(trapTarget === target);
        console.log(property);
        console.log(receiver === proxy);   
    }
}
const proxy = new Proxy(target, handler);
proxy.foo;

//输出
//true
//foo
//true

所以在技术上无论是get函数里,还是track函数里,target是原始对象。

3.2.2 怎么理解target

看一下proxy最基础的示例:

const target={
    id:'target'
};
const handle={};
const proxy = new Proxy(target,handle);

// id 属性会访问同一个值
console.log(target.id); // target
console.log(proxy.id); // target

// 给目标(target)属性赋值会反映在两个对象上 ,因为两个对象访问的是同一个值
target.id = 'foo';
console.log(target.id); // foo
console.log(proxy.id); // foo

3.2.3 小结

简而言之,尽管track的参数是原始对象,但在Vue的响应式系统上下文中,我们可以将其等同于它的响应式代理,因为所有操作实际上都是针对这个代理执行的。

4. 源码解析 —— trigger

/**
 * trigger函数负责找到所有依赖于这些数据的副作用函数,
 * 并执行它们以更新视图或执行其他副作用
 *
 * @param target - 发生变化的响应式对象.
 * @param type - 变化的类型,由TriggerOpTypes枚举定义,如设置(SET)、添加(ADD)、删除(DELETE)等.
 * @param key - 发生变化的具体属性名.
 */
export function trigger(
  target: object,
  type: TriggerOpTypes,
  key?: unknown,
  newValue?: unknown, //新值(对于SET操作)
  oldValue?: unknown, //旧值(对于SET操作)
  oldTarget?: Map<unknown, unknown> | Set<unknown>, //旧的集合对象(对于集合类型,如Map、Set的变化)
) {
  /**
   * 寻找依赖:如果depsMap不存在,意味着target从未被追踪过依赖,直接返回
   */
  const depsMap = targetMap.get(target)
  if (!depsMap) {
    // never been tracked
    return
  }


  /**
   * 确定需要触发的依赖集合(deps):根据变化的类型(type)和具体的属性(key),确定需要触发的依赖集合
   * 特殊情况处理:如整个集合被清除(CLEAR)、数组长度变化、Map集合的特定操作等,都有针对性的逻辑来确定哪些依赖需要被触发
   */
  let deps: (Dep | undefined)[] = []
  if (type === TriggerOpTypes.CLEAR) {
    // collection being cleared
    // trigger all effects for target
    deps = [...depsMap.values()]
  } else if (key === 'length' && isArray(target)) {
    const newLength = Number(newValue)
    depsMap.forEach((dep, key) => {
      if (key === 'length' || (!isSymbol(key) && key >= newLength)) {
        deps.push(dep)
      }
    })
  } else {
    // schedule runs for SET | ADD | DELETE
    if (key !== void 0) {
      deps.push(depsMap.get(key))
    }

    // also run for iteration key on ADD | DELETE | Map.SET
    switch (type) {
      case TriggerOpTypes.ADD:
        if (!isArray(target)) {
          deps.push(depsMap.get(ITERATE_KEY))
          if (isMap(target)) {
            deps.push(depsMap.get(MAP_KEY_ITERATE_KEY))
          }
        } else if (isIntegerKey(key)) {
          // new index added to array -> length changes
          deps.push(depsMap.get('length'))
        }
        break
      case TriggerOpTypes.DELETE:
        if (!isArray(target)) {
          deps.push(depsMap.get(ITERATE_KEY))
          if (isMap(target)) {
            deps.push(depsMap.get(MAP_KEY_ITERATE_KEY))
          }
        }
        break
      case TriggerOpTypes.SET:
        if (isMap(target)) {
          deps.push(depsMap.get(ITERATE_KEY))
        }
        break
    }
  }

  /**
   * 触发依赖更新:
   * 遍历所有需要触发的依赖集合deps,对于每个依赖(即副作用函数集合),调用triggerEffects函数来实际执行它们。
   * 在执行依赖前,调用pauseScheduling暂停调度(防止在依赖执行过程中产生无限循环调用),执行完后调用resetScheduling恢复调度。
   */
  pauseScheduling()
  
  for (const dep of deps) {
    if (dep) {
      triggerEffects(
        dep,
        DirtyLevels.Dirty,
        __DEV__
          ? {
              target,
              type,
              key,
              newValue,
              oldValue,
              oldTarget,
            }
          : void 0,
      )
    }
  }
  resetScheduling()
}

5. 应用场景

5.1 组件渲染

Vue 组件的渲染过程本身是一个副作用。Vue 使用reactiveEffect来自动重新渲染组件,当组件依赖的响应式数据变化时。

5.2 计算属性

计算属性是基于其它响应式数据计算得出的值。Vue内部使用reactiveEffect来跟踪计算属性依赖的数据,确保它们在依赖数据变化时更新。

5.3 观察者(watch)

Vue的watchAPI允许你观察响应式数据的变化,并在变化时执行回调函数。这背后也是使用reactiveEffect实现的。

6. 小结

通过本章的学习,我们了解了reactiveEffect在Vue 3响应式系统中的核心作用:依赖收集与更新触发。
而这里涉及的一些关键逻辑,如:shouldTrack、activeEffect等,我们会在下一个章节继续探讨。

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

绿联 部署vocechat,搭建私人聊天服务器,用于小型团队和家庭环境

1、镜像 privoce/vocechat-server:latest 2、安装 2.1、基础设置 重启策略&#xff1a;容器退出时总是重启容器。 2.2、网络 桥接即可。 2.3、存储空间 装载路径&#xff1a;/home/vocechat-server/data不可变更&#xff0c;权限读写。 2.4、端口设置 容器端口3000不可变…

备考ICA----Istio实验14---出向流量管控Egress Gateways实验

备考ICA----Istio实验14—出向流量管控Egress Gateways实验 1. 发布测试用 pod kubectl apply -f istio/samples/sleep/sleep.yaml kubectl get pods -l appsleep2. ServiceEntry 创建一个ServiceEntry允许流量访问edition.cnn.com egressgw/edition-ServiceEntry.yaml api…

基于springboot+vue+Mysql的家政服务管理平台

开发语言&#xff1a;Java框架&#xff1a;springbootJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;…

C# 多线程

文章目录 C# 多线程进程与线程无参数的子线程带参数的子线程运行结果 销毁线程 Abort()运行结果 ThreadPool和Task运行结果 C# 多线程 进程与线程 进程&#xff1a;进程就是一个应用程序&#xff0c;对电脑的各种资源的占用 线程&#xff1a;线程是程序执行的最小单位&#…

动力电池产业迎来周期性调整,宁德时代开启新角逐?

大爆发之后&#xff0c;新能源电池行业正在迈入紧张的下半场&#xff0c;一些数据显现出迹象。 根据业绩预告情况&#xff0c;目前57家锂电企业中&#xff0c;有45家企业的净利润负增长。另外&#xff0c;据中国汽车动力电池产业创新联盟数据&#xff0c;2月我国动力电池装车量…

【python plotly库介绍】从视觉到洞见:桑基图在业务分析中的应用【保姆级教程过于详细珍藏版】

&#x1f464;作者介绍&#xff1a;10年大厂数据\经营分析经验&#xff0c;现任大厂数据部门负责人。 本文结构&#xff1a;工具介绍python实现库 - 案例 - 分析思路与过程 实战专栏&#xff1a;https://blog.csdn.net/cciehl/category_12615648.html 备注说明&#xff1a; 企业…

Vue实现图片浏览器

结合之前所学到的v-show&#xff0c;v-on&#xff0c;v-bind写一个图片浏览器。 整个图片浏览器有两个按钮&#xff0c;“上一张”按钮会切换到上一张图片&#xff0c;“下一张”按钮会切换到下一张。切在第一张图片时&#xff0c;“上一张”按钮会隐去不见&#xff1b;同理&a…

星闪BLE与蓝牙

蓝牙与低功耗蓝牙 蓝牙工作在2.4GHz的频段&#xff0c;在工作频段2402MHz-2480MHz上分为79个信道&#xff0c;低功耗蓝牙分为40个信道。一组设备通信的时候&#xff0c;短暂占用一个信道发包&#xff0c;由于其跳频技术&#xff0c;每次传输完数据包之后都会切换到另一个信道继…

云渲染实用工具:3ds max怎么改低版本?

3ds Max是建模领域广泛采用的专业软件&#xff0c;它通过定期更新来不断增强功能和提升性能。但这些频繁的更新有时会导致一些插件暂时无法与新版本完全兼容。为了解决这个问题&#xff0c;设计师们可以采用一个简单有效的方法&#xff0c;那就是将较新版本的3ds Max文件进行版…

js的一些底层

数据类型 按照存储方式&#xff0c;JavaScript的数据类型可以分为两种&#xff0c;原始数据类型&#xff08;原始值&#xff09;和引用数据类型&#xff08;引用值&#xff09;。 原始数据类型目前有六种&#xff0c;包括Number、String、Boolean、Null、Undefined、Symb…

大模型助力学术图谱挖掘!OAG-Challenge @ KDD Cup 2024正式开始

如何用大模型推动学术知识图谱挖掘任务&#xff1f;OAG-Challenge KDD Cup 2024 邀你来赛&#xff01; 竞赛网址&#xff1a;https://www.biendata.xyz/kdd2024/ 竞赛背景 自 1997 年创办以来&#xff0c;知识发现和数据挖掘会议 ACM SIGKDD 上举办的 KDD Cup 被誉为全球最…

【2024】使用zabbix监控ESXI 6.5虚拟化系统

本次实验将采用docker部署zabbix 5.2平台监控ESXI 6.5虚拟化系统—————————————————————————— 请自行准备环境: 关于docker部署方案请参考: docker之核心概念与安装 关于docker部署zabbix方案请参考: docker容器方式部署zabbix监控平台 关于ESXI安…

009_lhs_rhs_in_Matlab中的左值和右值约定

Matlab中的左值和右值约定 1. 左值和右值 我们在把Matlab作为计算器来使用时&#xff0c;有些时候会直接列出一个表达式&#xff0c;等着Matlab打印出计算结果。就这个简单的举动&#xff0c;在计算机科学中&#xff0c;也有一个高大上的名字&#xff0c;叫什么REPL&#xff…

缓冲区溢出漏洞学习总结(漏洞原理及其利用方法)

文章目录 前言1、缓冲区溢出漏洞概述1.1、漏洞概述1.2、缓冲区溢出漏洞概述1.3、缓冲区溢出攻击概述1.4、引发缓冲区溢出的原因 2、栈溢出漏洞2.1、栈溢出漏洞概述2.2、栈溢出漏洞利用2.2.1、利用方法一&#xff1a;修改返回地址2.2.2、利用方法二&#xff1a;覆盖临接变量 3、…

多视图三维重建-SFM简介

背景 掌握传统的多视图三维重建基本流程 总体流程 多视图三维重建的Pipieline如下图&#xff0c;总共分为四个步骤&#xff1a; 拍摄场景多视角的图像建立这些图像之间的联系&#xff08;Data Association&#xff09;SFM稀疏重建MVS稠密重建 Data Association 建立图像…

第十二章 微服务核心(一)

一、Spring Boot 1.1 SpringBoot 构建方式 1.1.1 通过官网自动生成 进入官网&#xff1a;https://spring.io/&#xff0c;点击 Projects --> Spring Framework&#xff1b; 拖动滚动条到中间位置&#xff0c;点击 Spring Initializr 或者直接通过 https://start.spring…

QT_day3:信号和槽的连接方式

1、使用手动连接&#xff0c;将登录框中的取消按钮使用qt4版本的连接到自定义的槽函数中&#xff0c;在自定义的槽函数中调用关闭函数 将登录按钮使用qt5版本的连接到自定义的槽函数中&#xff0c;在槽函数中判断ui界面上输入的账号是否为"admin"&#xff0c;密码是…

嵌入式3-29

今日作业&#xff1a;用fwrite 和 fseek功能&#xff0c;将一张bmp格式的图片更改成 德国国旗#include <stdio.h> #include <string.h> #include <stdlib.h> #include <math.h> typedef unsigned char bgr[3]; int main(int argc, const char *argv[])…

用grafana+prometheus+cadvisor监控容器指标数据,并查询当前容器的网速网络用量

前言 整理技术&#xff0c;在这篇文章中&#xff0c;将会搭建grafanaprometheuscadvisor监控容器&#xff0c;并使用一个热门数据看板&#xff0c;再监控容器的性能指标 dashboard效果 这个是node-exporter采集到的数据&#xff0c;我没装node-exporter&#xff0c;而且这也…

3D人体姿态估计项目 | 从2D视频中通过检测人体关键点来估计3D人体姿态实现

项目应用场景 人体姿态估计是关于图像或视频中人体关节的 2D 或 3D 定位。一般来说&#xff0c;这个过程可以分为两个部分&#xff1a;(1) 2D 视频中的 2D 关键点检测&#xff1b;(2) 根据 2D 关键点进行 3D 位姿估计。这个项目使用 Detectron2 从任意的 2D 视频中检测 2D 关节…