当前位置: 首页 > ops >正文

《Vue.js》阅读之响应式数据与副作用函数

Vue.js

《Vue.js设计与实现》(霍春阳)

  • 适合:从零手写Vue3响应式系统,大厂面试源码题直接覆盖。
  • 重点章节:第4章(响应式)、第5章(渲染器)、第8章(编译器)

因为我第一周的任务就是响应式原理(Proxy vs defineProperty),手写简易reactive,所以我是从第四章开始学习的。

4.1 响应式数据与副作用函数

1、副作用函数

函数的执行会直接或间接影响其他函数的执行

比如:

01 function effect() {
02   document.body.innerText = 'hello vue3'
03 }

它的执行会使整个body的值都为hello vue3

2、响应式数据

01 const obj = { text: 'hello world' }
02 function effect() {
03   // effect 函数的执行会读取 obj.text
04   document.body.innerText = obj.text
05 }

如上面的这一段代码,我们希望当text发生变化的时候,effect会自动执行,这就是所谓的响应式数据

下面我们将会讲到这是怎么实现的:

● 当副作用函数 effect 执行时,会触发字段obj.text 的读取操作;
● 当修改 obj.text 的值时,会触发字段 obj.text的设置操作。
实现一个响应式的数据:拦截读和写两个步骤,
读的时候把effect函数放在一个容器里面
修改的时候就把这个effect函数释放出来
拦截一个对象的读取和设置操作:Proxy

  const data = { text: 'hello' }; // 定义数据对象const bucket = new Set(); // 设置一个容器用于存储副作用函数const obj = new Proxy(data, {// 读的时候拦截get(target, key) {bucket.add(effect); // 将当前活跃的副作用函数添加到容器return target[key]; // 返回属性值},// 拦截设置操作set(target, key, newVal) {target[key] = newVal;bucket.forEach(fn => fn()); // 执行所有存储的副作用函数return true;}});const  effect = () => {document.body.innerText = obj.text; // 副作用函数依赖于响应式数据};effect();setTimeout(() => {obj.text = "world"; // 修改数据,触发响应式更新}, 1000); // 延迟1秒修改数据

继续强化->我们现在硬编码了effect,但是如果副作用函数的名字不叫effect的话,这段代码就无法继续工作了

所以我们要提供一个用来注册副作用函数的机制

  // 用一个全局变量存储被注册的副作用函数let activeEffect;function effect (fn) {activeEffect = fn ; fn();}const bucket = new Set(); //设置一个容器const data = { text: 'world' }; // 初始化数据对象const obj = new Proxy(data , {//读的时候拦截放在容器里面get( tartget, key ){if(activeEffect){bucket.add(activeEffect);}return tartget[key];//返回属性值},set(target , key , newVal){// 拦截设置操作target[key] = newVal;bucket.forEach(fn => fn());return true;}})effect(() => {console.log('run');document.body.innerText = obj.text;})setTimeout(() => {obj.text = "hello"} , 1000)

当我们为obj添加新的属性的时候

  setTimeout(() => {obj.notExist = "hello vue3"} , 1000)

匿名副作用函数内没有读取这个新的属性的值,那么在1s之后不会起到写操作(即放出桶里面的所有函数),但是我尝试了一下,发现它执行了的。

我们为了解决这个问题,就要重新设计“桶”这个数据结构:让它无论读取的哪一个属性都会将副作用函数收到桶里面,设置属性的时候,无论设置的是哪一个属性,也都会将副作用函数取出并执行。

  let activeEffect;function effect (fn) {activeEffect = fn; fn();}const bucket = new WeakMap();const data = { text: 'world' }; // 确保所有属性都已定义const obj = new Proxy(data, {get(target, key){if(!activeEffect){return target[key];}// 根据tartget取来的depsMap,它是一个map类型let depsMap = bucket.get(target);// 如果不存在if(!depsMap){// 创建一个bucket.set(target, (depsMap = new Map()));}// 根据key取来的deps,它是一个set类型let deps = depsMap.get(key);// 如果不存在if(!deps){// 创建一个depsMap.set(key, (deps = new Set()));}deps.add(activeEffect); // 添加当前活跃的副作用函数return target[key];},set(target, key, newVal){target[key] = newVal;const depsMap = bucket.get(target);if(!depsMap){return;}const effects = depsMap.get(key);effects && effects.forEach(fn => fn()); // 只触发与键相关的副作用函数}});effect(() => {console.log('run');document.body.innerText = obj.text;});setTimeout(() => {obj.text = "hello vue3"; // 修改已定义的属性以触发依赖}, 1000);

大家可以发现,我们引用了weakMap(它与map最大的不同就是它对key是弱引用,不影响垃圾回收器的工作,通常存储只有当key所引用的对象存在时,才有价值的信息);

我们要解决的是属性不存在时候的问题,那么

  • 在读的时候判断是否有这个属性,没有就创建一个,有的话就把函数放在桶里面。
  • 在修改的时候也是要判断

最后,我们将一些函数进行封装:

<script setup>let activeEffect;function effect (fn) {activeEffect = fn; fn();}const bucket = new WeakMap();const data = { text: 'world' }; // 确保所有属性都已定义const obj = new Proxy(data, {get(target, key){track(target , key);return target[key];},set(target, key, newVal){target[key] = newVal;trigger(target , key , newVal);}});// 追踪变化function track(target , key){if(!activeEffect){return target[key];}// 根据tartget取来的depsMap,它是一个map类型let depsMap = bucket.get(target);// 如果不存在if(!depsMap){// 创建一个bucket.set(target, (depsMap = new Map()));}// 根据key取来的deps,它是一个set类型let deps = depsMap.get(key);// 如果不存在if(!deps){// 创建一个depsMap.set(key, (deps = new Set()));}deps.add(activeEffect); // 添加当前活跃的副作用函数}// 触发变化function trigger(target , key , newVal){const depsMap = bucket.get(target);if(!depsMap){return;}const effects = depsMap.get(key);effects && effects.forEach(fn => fn()); // 只触发与键相关的副作用函数}effect(() => {console.log('run');document.body.innerText = obj.text;});setTimeout(() => {obj.text = "hello vue3"; // 修改已定义的属性以触发依赖}, 1000);
</script>
http://www.xdnf.cn/news/5564.html

相关文章:

  • Hive HA配置高可用
  • 无线定位之 二 SX1302 网关源码 thread_down 线程详解
  • 奇次谐波和偶次谐波【EMC】
  • RabbitMQ ③-Spring使用RabbitMQ
  • 基于 Spring Boot 瑞吉外卖系统开发(十二)
  • labview硬件驱动——测试软件的安装(基于win11系统)
  • 支持向量机算法
  • K8s进阶之一文搞懂PV,PVC及SC
  • 修改网页标签处文字
  • kubuntu系统详解
  • 【RabbitMQ】应用问题、仲裁队列(Raft算法)和HAProxy负载均衡
  • 类和对象(1)--《Hello C++ Wrold!》(3)--(C/C++)
  • 【Linux笔记】——进程信号的保存
  • 51单片机引脚功能概述
  • 十五、多态与虚函数
  • labview硬件采集
  • 数字人教学技术与产品方案的全面解析
  • 42、在.NET 中能够将⾮静态的⽅法覆写成静态⽅法吗?
  • 本地不安装oracle,还想连oracle
  • c++STL-STL简介和vector的使用
  • ngx_http_keyval_module动态键值管理
  • 基于STM32、HAL库的RN8209C电能计量芯片驱动程序设计
  • 系统架构-嵌入式系统架构
  • AI 搜索引擎 MindSearch
  • 香港维尔利健康科技集团亮相中国资本市场发展年会,被评为“最具投资价值医疗科技企业”
  • 面试题解析 | C++空类的默认成员函数(附生成条件与底层原理)
  • 高吞吐与低延迟的博弈:Kafka与RabbitMQ数据管道实战指南
  • 互联网大厂Java求职面试:优惠券服务架构设计与AI增强实践-1
  • 七、基于HAL库,实现串口+DMA+状态机通信实现
  • 国产化Excel处理控件Spire.XLS系列教程:如何通过 C# 删除 Excel 工作表中的筛选器