【Vue3源码】第一章 effect和reactive

news2024/12/27 16:03:41

文章目录

  • 【Vue3源码】第一章 effect和reactive
    • 前言
    • 1、实现effect函数
    • 2、封装track函数(依赖收集)
    • 3、封装reactive函数
    • 4、封装trigger函数(依赖触发)
    • 5、单元测试

【Vue3源码】第一章 effect和reactive

前言

今天就正式开始Vue3源码学习了,那么很多初学者(包括我)在看vue源码时都会非常迷茫不知从何下手,所以我们在学习源码时应该反其道而行之,剔除掉源码中包括Tree-Shaking、TypeScript类型约束、特性开关、错误处理等操作,只了解其核心原理,大大提高学习效率减少学习成本!

如果你还不了解Vue源码设计“Tree-Shaking、TypeScript类型约束、特性开关、错误处理”?

可以购买一本《Vue.js设计与实现》,在第 2 章 “框架设计的核心要素” 有详细介绍了这些操作,并且有举例子解释了为什么要这么设计。

好了!正式开始学习,想要了解vue3的源码,我们可以先从reactivity文件夹入手。

那么reactivity文件夹是什么呢??

在这里插入图片描述

Reactivity:里面写的是vue最最重要的功能那就是“vue被人津津乐道并且顶顶大名的响应式源码“。

今天我们就简单的从零开始实现一遍effect函数(影响,效果),reactive函数(代理或者劫持)。

在reactive函数中还包括两个功能依赖收集依赖触发,他们分别是track函数(依赖收集)和trigger函数(依赖触发),在今天的文章中我都会一一实现一遍。

1、实现effect函数

effect可以说是响应式系统的核心,所以我们学习vue3源码时推荐从effect入手。

如果你还没有搭建好jest单元测试环境,可以查看我的上一篇文章

  1. 首先我们新建一个effect.ts文件并且封装一个effect函数
//用来暂存effect传递的参数fn
let activeEffect;

export const effect = (fn) => {
  activeEffect = fn
	fn()
};

​ 为什么我们还要在函数定义一个activeEffect块级作用域变量呢?

​ 因为activeEffect要帮我们暂存用户传进来的fn参数,fn的类型是一个函数,那么fn暂存到了activeEffect变量后为了不让下次传递的参数覆盖掉了我们的之前的fn,Vue就该把它存到一个“仓库”里保存着,方便后续管理,这个过程叫依赖收集

​ 我们再优化一下代码方便后续管理。

class ReactiveEffect {
  private _fn;
  constructor(fn) {
    this._fn = fn;
  }

  run() {
    activeEffect = this;
    this._fn();
  }
}

export const effect = (fn) => {

  const _effect = new ReactiveEffect(fn);
  _effect.run();

};	

那么怎么才能做到依赖收集呢?往下看⬇️

2、封装track函数(依赖收集)

在effect.ts文件中封装track函数

为了方便理解,我把targetMap抽象的比做一个仓库方便大家理解,作为一个仓库,自然就得有仓库的管理规则,货物进来总不能不分类就乱七八糟直接存进去吧?

所以我们就根据传递进来的参数,target(一个对象),key(该对象中的key)作为仓库的类别,一一细化分类这个仓库里包含的货物。

//targetMap就是一个收集effect传递过来的参数fn的“总仓库”
const targetMap = new WeakMap();

export function track(target, key) {
  //根据target一级分类
  let depsMap = targetMap.get(target);
  //如果这个货物是第一次存,我们就新建这个分类
  if (!depsMap) {
    depsMap = new Map();
    targetMap.set(target, depsMap);
  }
	//根据key二级分类
  let dep = depsMap.get(key);
  //如果这个货物是第一次存,我们就新建这个二级分类
  if (!dep) {
    dep = new Set();
    depsMap.set(key, dep);
  }
  //最后把收集到的activeEffect存入这个细化的分类里
  dep.add(activeEffect);
}

​ 所以targetMap作为一个总仓库,它通过传入的target和key进行分类保存我们的activeEffect。

​ 我画一个drawio图,看一下就可以明白里面的逻辑。

在这里插入图片描述

​ 那么我们的track函数接收的target和key参数又是怎么来的???往下看⬇️

3、封装reactive函数

我们都知道,vue3使用的是Proxy代理对象实现了数据响应式,Proxy(MDN对Proxy的介绍)代理多达13种捕获器它们可以完美的监听任何方式的数据改变,完美的解决了的vue2使用Object.defineProperty监听不到数组下标缺点。

不过说到缺点:真是非常的尴尬。。。不管Vue2的劫持还是Vue3的代理对有深层嵌套的对象还是要用递归去处理并且返回多层的响应式对象。

我们今天的文章只处理没有嵌套关系的对象,不深入多层嵌套对象的递归处理,下次再处理这个逻辑。

极简版reactive函数很简单,return反射的结果之前进行依赖收集即可,传入的target正是代理对象,key是正在使用对象的key。

新建一个reactive文件

import { track, trigger } from "./effect"

export const reactive = (raw) => {
    return new Proxy(raw,{
        get(target,key,receiver) {
            const res =  Reflect.get(target,key,receiver)

            //do something 收集依赖
            track(target,key)
            return res

        },
        set(target,key,value,receiver) {
            const res = Reflect.set(target,key,value,receiver)

            // do something 触发依赖
            trigger(target,key)
            return res
        },

    })
}

4、封装trigger函数(依赖触发)

​ 上一步封装好了reavtive函数的还差最后一个trigger函数我们还没有封装。

​ 其实依赖触发也很简单,通过传入的target和key(货物的分类类别),我们就可以从总仓库里取出收集到的对应依赖,并且再出触发它!

​ 在effect.ts文件中封装trigger

export function trigger(target, key) {
  let depsMap = targetMap.get(target);
  let dep = depsMap.get(key);
  for (let effect of dep) {
    effect.run();
  }
}

5、单元测试

我们新建在test文件夹下新建一个 effect.spec.ts文件

然后就可以打上断点跟着断点走一遍Vue响应式的执行帮助我们理解Vue的逻辑。
在这里插入图片描述

我测试的代码如下:

import { effect } from "../effect";
import { reactive } from "../reactive";

describe("effect", () => {
  it("happy path", () => {
    const user = reactive({
      age: 10,
      name:'www',
      newObj:{
        objAge:11
      }
    });
    let nextAge;
    let age2
    effect(() => {
      nextAge =user.age + 1;
    });
    //无法代理深层嵌套的对象
    effect(() => {
      age2 = user.newObj.objAge;
    });
    expect(nextAge).toBe(11);
    user.age++;
    expect(nextAge).toBe(12);
    user.age = 99
    expect(nextAge).toBe(100)

    expect(age2).toBe(11)

    //对于深层嵌套的对象由于没有封装递归的逻辑所以监听不到
    user.newObj.objAge ++
    //理论上来说age2应该跟着user.newobj.objeAge响应式变成12,而结果却没有变化
    expect(age2).toBe(11)

  });
});

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

驱动 | Linux | NVMe 不完全总结

本文主要参考这里 1’ 2 的解析和 linux 源码 3。 此处推荐一个可以便捷查看 linux 源码的网站 bootlin 4。 更新:2022 / 02 / 11 驱动 | Linux | NVMe 不完全总结NVMe 的前世今生从系统角度看 NVMe 驱动NVMe CommandPCI 总线从架构角度看 NVMe 驱动NVMe 驱动的文件…

vue+nodejs考研资料分享系统vscode - Visual Studio Code

前端技术:nodejsvueelementui,视图层其实质就是vue页面,通过编写vue页面从而展示在浏览器中,编写完成的vue页面要能够和控制器类进行交互,从而使得用户在点击网页进行操作时能够正常。 Express 框架于Node运行环境的Web框架, 目 …

计算机网络5:数据在两台计算机之间是怎样传输的?

数据在两台计算机之间的传输总的来说包括了封装和解封两个过程 封装(5层协议) 以传送一张图片为例 **应用层:**将jpg格式的图片数据转化成计算机可以识别的0101的二进制的比特流 **传输层:**将应用层传输下来的数据进行分段&…

pandas数据分析35——多个数据框实现笛卡尔积

什么是笛卡尔积。就是遍历所有组合的可能性。 比如第一个盒子有[1,2,3]三个号码球,第二个盒子有[4,5]两个号码球。那么从每个盒子里面分别拿一个球共有3*2两种可能性,其集合就是{[1,4],[2,4],[3,4],[1,5],[2,5],[3,5]},这个就是笛卡尔积。 三个盒子也是…

GhostNet v2(NeurIPS 2022 Spotlight)原理与代码解析

paper:GhostNetV2: Enhance Cheap Operation with Long-Range Attentioncode:https://github.com/huawei-noah/Efficient-AI-Backbones/tree/master/ghostnetv2_pytorch背景在智能手机和可穿戴设备上部署神经网络时,不仅要考虑模型的性能&…

批量分析快递单号派件时效技巧

规定,如果出现派件延误商家没有及时处理,那么平台会对商家进行采取措施等等,同时商家每天都去跟踪自己发出单号物流状况,一个一个去查呢,不大可能,他们量是比较大,一个一个手动去官方查询工作量…

从元宇宙被认识的第一天开始,就注定了逃不脱区块链所经历的命运

从元宇宙被人们认识的第一天开始,它就注定了依然逃不脱它的先辈——区块链所经历的命运。如果对这样一种宿命进行一个总结的话,我更加愿意将其归结为以资本为主导的野蛮生长的发展模式。这种发展模式并不仅仅只是在区块链和元宇宙的身上出现,…

2016-ICLR-Order Matters- Sequence to sequence for sets

2016-ICLR-Order Matters- Sequence to sequence for sets Paper: [https://arxiv.org/pdf/1511.06391.pdf](https://arxiv.org/pdf/1511.06391.pdf) Code: 顺序重要性:集合的顺序到序列 摘要 许多需要从观察序列映射或映射到观察序列的复杂任务现在可以使用序列…

C++类基础(十一)

运算符重载&#xff08;二&#xff09; ● 对称运算符通常定义为非成员函数以支持首个操作数的类型转换 struct Str {int val 0;Str(int input): val(input){}auto operator(Str x){std::cout << "auto operator(Str x)\n";return Str(val x.val);} }; int …

git 常用命令之 git log

大家好&#xff0c;我是 17。 git log dev 查看 dev 分支的 log git log -p 每次提交所引入的差异 git log --stat 每次提交的简略统计信息 git log --name-status 比 --stat 更简洁&#xff0c;更常用。git log -2 最近两次提交 git log --since2.weeks 最近两周内的提交 git…

Linux通配符、转义符讲解

目录 通配符 通过通配符定义匹配条件 转义符 将所有的逻辑操作符都转换成字符 通配符 通过通配符定义匹配条件 * 任意字符都可以通配&#xff08;也可以匹配空值&#xff09; &#xff1f; 匹配单个字符 [a-z] 匹配单个的小写英文字母 [A-Z] 匹配单个的大写英文…

jQuery 基础知识(五)

前面已经介绍HTML、CSS和javaScript&#xff0c;这三个前端语言&#xff0c;俗称前端三剑客。下面来学习一个javaScript简化并封装好的库——jQuery库。01 - AJAX AJAX 是一种在无需重新加载整个网页的情况下&#xff0c;能够更新部分网页的技术。 AJAX的介绍 ajax 是 Asynchr…

freesurfer如何将组模板投影到个体空间——如投影 Schaefer2018 到个体空间

freesurfer如何将组模板投影到个体空间——如投影 Schaefer2018 到个体空间 freesurfer如何将组模板投影到个体空间? freesurfer如何将组模板投影到个体空间——如投影 Schaefer2018 到个体空间freesurfer的整理流程freesurfer的安装freesurfer对结构像分割流程及批处理代码fr…

git 常用命令之 git checkout

大家好&#xff0c;我是 17。 git checkout 是 git 中最重要最常用的命令之一&#xff0c;本文为大家详细解说一下。 恢复工作区 checkout 的用途之一是恢复工作区。 git checkout . checkout . 表示恢复工作区的所有更改,未跟踪的文件不会有变化。 恢复工作区的所有文件风…

硬件工程师入门基础知识(一)基础元器件认识(二)

硬件工程师入门基础知识 &#xff08;一&#xff09;基础元器件认识&#xff08;二&#xff09; tips&#xff1a;学习资料和数据来自《硬件工程师炼成之路》、百度百科、网上资料。 1.二极管 2.三极管 3.MOS管 4.IGBT 5.晶振 1.二极管 肖特基二极管和硅二极管的比较&#…

架构方法论

0.缘起最近在和同事以及相关领域的人沟通时&#xff0c;大家都在强调架构、架构图&#xff0c;于是兴起了一片关于架构的方法论介绍。本文对内容的组织按照顶层设计思路&#xff0c;先对架构本身进行剖析&#xff1a;什么是架构&#xff1f;为什么架构很重要&#xff1f;这些是…

大家心心念念的RocketMQ5.x入门手册来喽

1、前言 为了更好的拥抱云原生&#xff0c;RocketMQ5.x架构进行了大的重构&#xff0c;提出了存储与计算分离的设计架构&#xff0c;架构设计图如下所示&#xff1a; RocketMQ5.x提供了一套非常建议的消息发送、消费API&#xff0c;并统一放在Apache顶级开源项目rocketmq-clie…

T07 奥运排序问题

描述 按要求&#xff0c;给国家进行排名。 输入描述&#xff1a; 有多组数据。 第一行给出国家数N&#xff0c;要求排名的国家数M&#xff0c;国家号从0到N-1。 第二行开始的N行给定国家或地区的奥运金牌数&#xff0c;奖牌数&#xff0c;人口数&#xff08;百万&#xff09…

【c/c++】c语言的自增操作在不同编译器的差别

示例代码 代码如下&#xff1a; #include <stdio.h>#define product(x) ((x)*(x))int main(void) {int i 3, j, k;j product(i); // (i) * (i)k product(i); // (i) * (i)printf("%d %d\n", j, k); }执行结果 在Ubuntu18.04下通过GCC编译和执行的结果…

【在执行make geth报错解决方法】

在执行make geth报错解决方法问题详细描述&#xff1a;详细解决方法对根据报错提示信息对相关文件夹权限进行修改2、再次执行make geth 检查是否还报错问题详细描述&#xff1a; Ubuntu 版本&#xff1a;18.04问题&#xff1a;在编译运行以太坊源码执行make geth命令时报错&am…