【Vue3源码】第一章 effect和reactive

news/2024/11/28 11:37:55/

文章目录

  • 【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 = fnfn()
};

​ 为什么我们还要在函数定义一个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 age2effect(() => {nextAge =user.age + 1;});//无法代理深层嵌套的对象effect(() => {age2 = user.newObj.objAge;});expect(nextAge).toBe(11);user.age++;expect(nextAge).toBe(12);user.age = 99expect(nextAge).toBe(100)expect(age2).toBe(11)//对于深层嵌套的对象由于没有封装递归的逻辑所以监听不到user.newObj.objAge ++//理论上来说age2应该跟着user.newobj.objeAge响应式变成12,而结果却没有变化expect(age2).toBe(11)});
});

http://www.ppmy.cn/news/24353.html

相关文章

Dart语法基础补充

Asynchrony support Dart 库中充满了返回 Future 或 Stream 对象的函数。 这些函数是异步的:它们在设置一个可能耗时的操作(例如 I/O)后返回,而不等待该操作完成。 async 和 await 关键字支持异步编程,让编写看起来类…

Fluent Python 笔记 第 8 章 对象引用、可变性和垃圾回收

本章先以一个比喻说明 Python 的变量:变量是标注,而不是盒子。如果你不知道引用式变量是什么,可以像这样对别人解释别名。 然后,本章讨论对象标识、值和别名等概念。随后,本章会揭露元组的一个神奇特性:元…

【Hello Linux】 Linux的权限以及Shell原理

作者:小萌新 专栏:Linux 作者简介:大二学生 希望能和大家一起进步! 本篇博客简介:介绍Linux的基础命令 Linux的权限以及Shell原理Shell的运行原理权限Linux中权限的概念如何切换用户如何提升当前操作的权限如何添加信任…

【前端vue2面试题】2023前端最新版vue模块,高频17问(上)

🥳博 主:初映CY的前说(前端领域) 🌞个人信条:想要变成得到,中间还有做到! 🤘本文核心:博主收集的关于vue2面试题(上) 目录 vue2面试题 1、$route 和 $router的区别 2、一个…

【随笔】我迟到的2022年度总结:突破零粉丝,1个月涨粉1000+,2023年目标3万+

前言 我是21年12月注册的csdn, 作为用户平时看看文章,从未参与过写文章这件事。 但这一年的时间我见证了很多新号的崛起,有的号我平时关注比较多,看着他们从零粉丝突破了三万甚至五万的粉丝量。 在csdn上遇到了我的贵人&#x…

湿敏电阻的原理,结构,分类与应用总结

🏡《总目录》 0,概述 湿敏电阻是指电阻值随着环境的湿度变化而变化的电阻,本文对其工作原理,结构,分类和应用场景进行总结。 1,工作原理 湿敏电阻是利用湿敏材料制成的,湿敏材料吸收空气中水分时,自身的阻值发生变化。 2,结构 如下图所示,市民电阻包括4个部分构成,…

【JavaWeb项目】简单搭建一个前端的博客系统

博客系统项目 本项目主要分成四个页面: 博客列表页博客详情页登录页面博客编辑页 该系统公共的CSS样式 common.css /* 放置一些各个页面都会用到的公共样式 */* {margin: 0;padding: 0;box-sizing: 0; }/* 给整个页面加上背景 */ html, body{height: 100%; }body {backgrou…

【MySQL】 事务

😊😊作者简介😊😊 : 大家好,我是南瓜籽,一个在校大二学生,我将会持续分享Java相关知识。 🎉🎉个人主页🎉🎉 : 南瓜籽的主页…