Vue 进阶系列丨实现简易reactive和ref

a477a844fff6dfdf4a7830aa483224d9.png

Vue 进阶系列教程将在本号持续发布,一起查漏补缺学个痛快!若您有遇到其它相关问题,非常欢迎在评论中留言讨论,达到帮助更多人的目的。若感本文对您有所帮助请点个赞吧!

2013年7月28日,尤雨溪第一次在 GItHub 上为 Vue.js 提交代码;2015年10月26日,Vue.js 1.0.0版本发布;2016年10月1日,Vue.js 2.0发布。

最早的 Vue.js 只做视图层,没有路由, 没有状态管理,也没有官方的构建工具,只有一个库,放到网页里就可以直接用了。

后来,Vue.js 慢慢开始加入了一些官方的辅助工具,比如路由(Router)、状态管理方案(Vuex)和构建工具(Vue-cli)等。此时,Vue.js 的定位是:The Progressive Framework。翻译成中文,就是渐进式框架。

Vue.js2.0 引入了很多特性,比如虚拟 DOM,支持 JSX 和 TypeScript,支持流式服务端渲染,提供了跨平台的能力等。Vue.js 在国内的用户有阿里巴巴、百度、腾讯、新浪、网易、滴滴出行、360、美团等等。

Vue 已是一名前端工程师必备的技能,现在就让我们开始深入学习 Vue.js 内部的核心技术原理吧!


响应式原理

在前端开发中,"响应式"通常指的是用户界面对数据的变化做出相应的能力。换句话说,当数据发生变化时,界面能够自动更新以反映这些变化。这种机制可以让开发者专注于数据和业务逻辑,而不必手动管理界面的更新。

在Vue.js中,响应式是框架的核心特性之一。在Vue 3中,响应式的原理主要依赖于ES6中的Proxy对象。

具体来说,Vue 3的响应式原理包括以下几个步骤:

  1. 初始化阶段:当你创建一个Vue实例或者定义一个响应式对象时,Vue会对数据进行初始化。在初始化阶段,Vue会使用Proxy对象来监听数据的变化。

  2. Getter和Setter:对象被Proxy包裹后,每个属性都会有对应的Getter和Setter函数。当你访问响应式对象的属性时,会触发Getter函数,Vue会将这个属性与当前的组件实例关联起来,这样Vue就知道哪些组件依赖于这个属性。当属性被修改时,会触发Setter函数,Vue会通知所有依赖于该属性的组件进行更新。

  3. 依赖追踪:Vue使用依赖追踪来跟踪数据属性与组件之间的关联关系。每个组件都有一个依赖收集器,用于存储与该组件相关的所有数据属性。当属性被访问时,Vue会将当前组件与这个属性建立关联,并将属性的变化依赖于这个组件。

  4. 触发更新:当响应式对象的属性被修改时,会触发Setter函数。Setter函数会通知所有依赖于这个属性的组件进行更新,从而使界面能够反映数据的变化。

总的来说,Vue 3的响应式原理利用了ES6中的Proxy对象来实现数据的监听和依赖追踪,从而实现了高效的数据响应式更新。这种机制让Vue能够在数据发生变化时自动更新相关的界面组件,使开发者能够更加专注于业务逻辑的实现。


实现reactive

开发思想,从单元测试出发,先定义自己想要的最终结果,然后逐步实现相关的API

第一步:这里呢,我们定义第一个单元测试

// reactive.spec.ts (这里用的单元测试为 jest)// 这里引入的是我们即将实现的自己的reactive
import { reactive } from "../reactive";
// 定义单元测试的标题为reactive,此处定义为hello world都可以
describe("reactive",()→{it("first case",()→{// 定义一个原生对象const original = {foo:1};// 此处用reactive包裹后返回一个对象const observed = reactive(original);// 期待observed的值不等于originalexpect(observed).not.toBe(original);// 期待observed.foo 为 1expect(observed.foo).toBe(1);});
});

根据上面测试的内容,我们可以实现这样一个reactive

// reactive.tsexport function reactive(raw) {// reactive 实际上返回的就是一个proxy对象return new Proxy(raw, {// 拦截getget(target, key) {const res = Reflect.get(target, key);return res;}
}

此时我们已经实现了一个简易的reactive,只不过还不支持依赖收集和触发依赖的逻辑。通过上文我们知道,vue3中依赖收集和触发依赖是在getter和setter中触发的,所以我们的代码可以写成下面这样:

// reactive.tsexport function reactive(raw) {return new Proxy(raw, {get(target, key) {const res = Reflect.get(target, key);// TODO 依赖收集track(target, key);return res;},set(target,key,value) {const res = Reflect.set(target, key, value);// TODO 触发依赖trigger(target, key)return res;}
}

此时我们只需要实现 track和trigger即可

下面我们看我们的第二个单元测试:

// effect.spec.tsimport { reactive } from '../reactive'
// 这里的effect也是我们后面将要实现的
import { effect } from '../effect'// effect 就是我们的依赖,也叫做副作用
describe("effect",()→{it("second case",()→{const user = reactive({age: 10,});let nextAge;effect(()→{nextAge=user.age + 1;});expect(nextAge).toBe(11);// updateuser.age++;expect(nextAge).toBe(12);});
});

可以看到上面的单元测试中定义了一个函数effect,effect 是一个函数,用于创建副作用。它是 Vue 3 中响应式 API 的一部分,用于处理响应式数据的变化。effect 函数接受一个回调函数作为参数,并在这个回调函数中定义副作用。当回调函数中依赖的响应式数据发生变化时,副作用将被重新执行

这里先简单说一下这个依赖收集和触发依赖是个怎么回事,可以假设这么一个场景:

1. 在火车站都有寄存包裹的地方,每个旅游团就是一个对象,旅游团的每个人就是对象的键。

2. 当人员去存储包裹的时候,寄存处会看当前人员属于哪个旅游团,相同的旅游团集中放到一个包裹柜,后续方便查找。

3. 然后在这个包裹柜上面找一个箱子给人员,并且给他一把箱子钥匙(依赖收集

4. 当相同的人员第二次存储包裹的时候,他会继续在原有的箱子里放新的东西(依赖收集

5. 以此类推

6. 当人员回来拿包裹时,会把钥匙给寄存处,寄存处会将钥匙对应的箱子里的所有东西拿出来(触发依赖

下面我们来实现effect:

// effect.tsclass ReactiveEffect {private _fn: any;constructor(fn) {this._fn=fn;}run(){activeEffect = thisthis._fn();     }
}// 所有依赖收集到的地方,可以理解成一个寄存处
const targetMap = new Map();
// 收集依赖
export function track(target, key) {let depsMap = targetMap.get(target);// 先看寄存处里面是否已经由当前对象对应的包裹柜if(!depsMap){depsMap = new Map();targetMap.set(target,depsMap);}let dep = depsMap.get(key)// 再看当前对象对应的键值,是否有对应的箱子if(!dep){dep = new Set();depsMap.set(key, dep)}// 最后将用户传入的fn作为依赖,添加进入箱子中trackEffects(dep)
}export function trackEffects(dep){dep.add(activeEffect);
}// 实现trigger
export function trigger(target, key) {// 先根据旅游团找到对应的包裹柜let depsMap = targetMap.get(target);// 根据人员找到对应的箱子let dep = depsMap.get(key);// 把箱子里所有的内容拿出来执行triggerEffects(dep)
}export function triggerEffects(dep){for(const effect of dep){effect.run();}
}let activeEffect;
export function effect(fn) {// fnconst _effect = new ReactiveEffect(fn)// 立即执行传入的函数_effect.run();
}

此时我们的reactive就实现完成了,这里做个总结:

  1. 就是每个键在getter的时候,也就是effect函数传入的时候(这里会触发getter),将整个effect函数作为依赖,放入键值对应的箱子里

  2. 当数据更新的时候,也就是触发setter时,将箱子里的内容(fn函数)拿出来执行一遍。此时,相关的响应式数据也就更新了


实现ref

有了上面reactive的基础,ref会相当简单的学会。我们还是通过一个单元测试开始:

// ref.spec.tsdescribe("ref",()→{it("first case",()={const a = ref(1);expect(a.value).toBe(1);});it("second case",()=>{            const a = ref(1);let dummy;let calls = 0;effect(()=>{calls++;dummy = a.value;}};expect(calls).toBe(1);expect(dummy).toBe(1);a.value = 2;expect(calls).toBe(2);expect(dummy).toBe(2);})
})

ref都是通过.value来触发,我们可以使用一个类,然后拦截他的get和set,这里给出最终代码:

// ref.tsclass RefImpl {private _value: any;// 存放依赖的箱子public dep;constructor(value) {this._value = value;this.dep = new Set();}get value(){// 收集依赖trackEffects(this.dep)return this._value;}set value(newValue){this.value = newValue// 触发依赖triggerEffects(this.dep)}
}
export function ref(value) {return new RefImpl(value);
}

Vue 进阶系列教程将在本号持续发布,一起查漏补缺学个痛快!若您有遇到其它相关问题,非常欢迎在评论中留言讨论,达到帮助更多人的目的。若感本文对您有所帮助请点个赞吧!

13fe43985a61ca71b4dca7984f403cd6.png

叶阳辉

HFun 前端攻城狮

往期精彩:

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

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

相关文章

计算机网络Day02--物理层(一)

计算机网络Day02–物理层 物理层基本概念 物理层考虑的是怎么才能在连接各种计算机的传输媒体上传输比特流,而不是具体的传输媒体 作用:尽可能屏蔽掉不同传输媒体和通信手段的差异 用于物流层的协议也称为物流层规程 主要作用:解决计算机…

Android---Jetpack Compose学习007

Compose 附带效应 a. 纯函数 纯函数指的是函数与外界交换数据只能通过函数参数和函数返回值来进行,纯函数的运行不会对外界环境产生任何的影响。比如下面这个函数: fun Add(a : Int, b : Int) : Int {return a b } “副作用”(side effe…

【蓝桥杯单片机入门记录】静态数码管

目录 一、数码管概述 (1)认识数码管 (2)数码管的工作原理 (3)LED数码管驱动方式-静态显示 二、数码管电路图 三、静态数码管显示例程 (1)例程1:数码管显示某一位&a…

PYQT5-自定义事件

from PyQt5.QtCore import QEvent, QObject from PyQt5.QtWidgets import QApplication import sys# 自定义事件类 class CustomEvent(QEvent):# PYQT5 预留给用户自定义事件类型的起点为 QEvent.User1000custom_event_type QEvent.registerEventType()# 也可以这样写# custom…

2024.2.22

P1162 #include<map> #include<vector> #include<iostream> #include<math.h> #include<algorithm> #include<string> using namespace std; const int N 1020; int n; int g[N][N];//标记数组 int a[N][N];//储存数组 int dx[] { -1…

webstorm光标变成方块解决办法_webstorm光标变粗不能换行

webstorms光标变了 键盘上的insert是切换的快捷键&#xff0c;敲insert就可以来回切换了

双通道并行网络,想用哪个网络用哪个,MATLAB代码

本期可谓是宝藏篇&#xff01;学会本期的思想&#xff0c;帮助你分分钟找到创新点&#xff0c;且不与别人重复&#xff01; 本期采用MATLAB代码&#xff0c;实现一种“基于格拉姆角场与并行CNN的故障诊断方法”。该方法的具体实现可以参考文献&#xff1a; [1]李宗源,陈谦,钱…

普中51单片机学习(EEPROM)

EEPROM IIC串行总线的组成及工作原理 I2C总线的数据传送 数据位的有效性规定 I2C总线进行数据传送时&#xff0c;时钟信号为高电平期间&#xff0c;数据线上的数据必须保持稳定&#xff0c;只有在时钟线上的信号为低电平期间&#xff0c;数据线上的高电平或低电平状态才允许…

分享WebGL物体三维建模

界面效果 代码结构 模型素材类似CT (Computed Tomography)&#xff0c;即电子计算机断层扫描&#xff0c;它是利用精确准直的X线束、γ射线、超声波等&#xff0c;与灵敏度极高的探测器一同围绕物体的某一部位作一个接一个的断面扫描。 坐标系统 渲染流程 渲染流程是个将之前准…

Sora:OpenAI引领AI视频新时代

Sora - 探索AI视频模型的无限可能 随着人工智能技术的飞速发展&#xff0c;AI视频模型已成为科技领域的新热点。而在这个浪潮中&#xff0c;OpenAI推出的首个AI视频模型Sora&#xff0c;以其卓越的性能和前瞻性的技术&#xff0c;引领着AI视频领域的创新发展。让我们将一起探讨…

【SpringCloud】使用 Spring Cloud Alibaba 之 Sentinel 实现微服务的限流、降级、熔断

目录 一、Sentinel 介绍1.1 什么是 Sentinel1.2 Sentinel 特性1.3 限流、降级与熔断的区别 二、实战演示2.1 下载启动 Sentinel 控制台2.2 后端微服务接入 Sentinel 控制台2.2.1 引入 Sentinel 依赖2.2.2 添加 Sentinel 连接配置 2.3 使用 Sentinel 进行流控&#xff08;含限流…

如何将cocos2d-x js打包部署到ios上 Mac M1系统

项目环境 cocos2d-x 3.13 xcode 12 mac m1 big sur 先找到你的项目 使用xcode软件打开上面这个文件 打开后应该是这个样子 执行编译运行就好了 可能会碰到的错误 在xcode11版本以上都会有这个错误&#xff0c;这是因为iOS11废弃了system。 将上面代码修改为 #if (CC_TARGE…

Java 面向对象进阶 16 接口的细节:成员特点和接口的各种关系(黑马)

成员变量默认修饰符是public static final的原因是&#xff1a; Java中接口中成员变量默认修饰符是public static final的原因是为了确保接口的成员变量都是公共的、静态的和不可修改的。 - public修饰符确保了接口的成员变量可以在任何地方被访问到。 - static修饰符使得接口…

vue-利用属性(v-if)控制表单(el-form-item)显示/隐藏

表单控制属性 v-if 示例&#xff1a; 通过switch组件作为开关&#xff0c;控制表单的显示与隐藏 <el-form-item label"创建数据集"><el-switch v-model"selectFormVisible"></el-switch></el-form-item><el-form-item label&…

Redis篇----第七篇

系列文章目录 文章目录 系列文章目录前言一、Redis 的回收策略(淘汰策略)?二、为什么 edis 需要把所有数据放到内存中?三、Redis 的同步机制了解么?四、Pipeline 有什么好处,为什么要用 pipeline?前言 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍…

crontab history查看命令的执行时间

crontab crontab学习网站&#xff08;19. crontab 定时任务 — Linux Tools Quick Tutorial&#xff09; 例子 今天实际工作里用到的&#xff08;已经进行了防信息泄露处理 比如我现在希望每周三上午10:00之行一个php脚本 --gpt生成 00 10 * * 3 cd /home/user/project/r…

阿里云SSL免费证书到期自动申请部署程序

阿里云的免费证书只有3个月的有效期&#xff0c;不注意就过期了&#xff0c;还要手动申请然后部署&#xff0c;很是麻烦&#xff0c;于是写了这个小工具。上班期间抽空写的&#xff0c;没有仔细测试&#xff0c;可能存在一些问题&#xff0c;大家可以自己clone代码改改&#xf…

【大模型 数据增强】LLMAAA:使用 LLMs 作为数据标注器

【大模型 数据增强】LLMAAA&#xff1a;使用 LLMs 作为数据标注器 提出背景算法步骤1. LLM作为活跃标注者&#xff08;LLMAAA&#xff09;2. k-NN示例检索与标签表述化3. 活跃学习策略4. 自动重权技术 LLMAAA 框架1. LLM Annotator2. Active Acquisition3. Robust Training 总结…

SkyWalking之APM无侵入可观测原理分析

一、 简介&#xff08;为什么需要用到可观测能力&#xff09; 随着微服务的开发模式的兴起&#xff0c;早期的单体架构系统已拆分为很多的子系统&#xff0c;各个子系统封装为微服务&#xff0c;各服务间通过HTTP协议RESET API或者RPC协议进行调用。 在单体服务或者微服务较少的…

8:00面试,8:05就出来了 ,问的实在是....

从外包出来&#xff0c;没想到竟然死在了另一家厂子 自从加入这家公司&#xff0c;每天都在加班&#xff0c;钱倒是给的不少&#xff0c;所以我也就忍了。没想到12月一纸通知&#xff0c;所有人都不许加班&#xff0c;薪资直降30%&#xff0c;顿时有吃不起饭的赶脚。 好在有个…