实现effect的stop和onStop功能

news2024/11/18 9:44:11

06_实现effect的stop和onStop功能

一、实现stop

(一)单元测试

it('stop', () => {let dummy;const obj = reactive({ prop: 1 });const runner = effect(() => {dummy = obj.prop;});obj.prop = 2;expect(dummy).toBe(2);stop(runner);obj.prop = 3;expect(dummy).toBe(2);runner();expect(dummy).toBe(3);
}); 

通过以上单测,可以很明显地看出来,可以通过stop函数传入runner去停止数据的响应式,而当重新手动执行runner的时候,数据又会恢复响应式。

(二)代码实现

从单测继续分析代码实现,通过stop函数传入runner,那就得继续回到effect.ts,首先导出一个stop函数。

export function stop(runner) {

} 

再开始完善stop函数。

继续分析:

通过runner停止当前effect的响应式 → 也就是从收集到当前effectdep中将其删除,实际上是对effect 的操作,所以继续在ReactiveEffect上维护一个stop方法。

class ReactiveEffect {private _fn: any;// 在构造函数的参数上使用public等同于创建了同名的成员变量constructor(fn, public scheduler?) {this._fn = fn;}run() {activeEffect = this;return this._fn();}stop() {}
} 

大致思路明白了,接下来解决第一个问题:如何通过runner找到ReactiveEffect的实例,然后去调用stop

答:在function effect() {}中将_effect挂载到runner上。

所以需要改写一下之前的代码:

export function effect(fn, options: any = {}) {const _effect = new ReactiveEffect(fn, options.scheduler);_effect.run();const runner: any = _effect.run.bind(_effect);runner.effect = _effect;return runner;
} 

那么我们导出的stop函数的逻辑就清晰了。

export function stop(runner) {runner.effect.stop();
} 

再来完善ReactiveEffect类的stop函数,也就是解决第二个问题:如何从收集到当前effectdep中将其删除?

答:此时,我们并不知道当前effect存在于哪些dep中,所以考虑从track时入手,在dep收集activeEffect后,让activeEffect 反向收集dep,这样,就知道了当前effect所在的dep,接下来删掉就行了。

dep.add(activeEffect);
activeEffect.deps.push(dep); 
class ReactiveEffect {private _fn: any;deps = [];// 在构造函数的参数上使用public等同于创建了同名的成员变量constructor(fn, public scheduler?) {this._fn = fn;}run() {activeEffect = this;return this._fn();}stop() {this.deps.forEach((dep: any) => {dep.delete(this);});}
} 

功能完成后,继续看一下单测结果。

(三)代码优化

在完成功能以后,重新考虑对之前代码的实现。

1.代码可读性的问题抽离将当前依赖从收集到的dep中删除的逻辑,命名为cleanupEffect,然后在类ReactiveEffectstop 中,直接调用cleanupEffect(this)即可。function cleanupEffect(effect: any) {effect.deps.forEach((dep: any) => {dep.delete(effect);});} 2.性能问题当多次调用stop时,实际上第一次已经删除了,后续调用都没有实际意义,只会引起无意义的性能浪费。 所以考虑给其一个active状态,当被cleanupEffect后,置为false,不再进行再次删除。class ReactiveEffect {private _fn: any;deps = [];active = true;// 在构造函数的参数上使用public等同于创建了同名的成员变量constructor(fn, public scheduler?) {this._fn = fn;}run() {activeEffect = this;return this._fn();}stop() {// 要从收集到当前依赖的dep中删除当前依赖activeEffect// 但是我们根本不知道activeEffect存在于哪些dep中,所以就要用activeEffect反向收集depif (this.active) {cleanupEffect(this);this.active = false;}}} * * *

二、实现onStop

(一)单元测试

it('onStop', () => {const obj = reactive({ prop: 1 });const onStop = jest.fn();let dummy;const runner = effect(() => {dummy = obj.foo;},{onStop,},);stop(runner);expect(onStop).toBeCalledTimes(1);
}); 

其实通过单测,可以看出功能跟stop有些类似,逻辑也很简单,就是通过effect的第二个参数,给定一个onStop 函数,当有这个函数时,我们再去调用stop(runner) 时,onStop就会被调用一次。

那么实现思路也就很清晰了,我们首先得在ReactiveEffect类中去接收这个函数,然后调用stop的时候,手动调用一下onStop即可。

(二)代码实现:

class ReactiveEffect {private _fn: any;deps = [];active = true;// + 定义函数可选onStop?: () => void;// 在构造函数的参数上使用public等同于创建了同名的成员变量constructor(fn, public scheduler?) {this._fn = fn;}run() {activeEffect = this;return this._fn();}stop() {// 要从收集到当前依赖的dep中删除当前依赖activeEffect// 但是我们根本不知道activeEffect存在于哪些dep中,所以就要用activeEffect反向收集depif (this.active) {cleanupEffect(this);// + 如果onStop有,就调用一次if (this.onStop) {this.onStop();}this.active = false;}}
}

export function effect(fn, options: any = {}) {const _effect = new ReactiveEffect(fn, options.scheduler);// + 接收onStop_effect.onStop = options.onStop;_effect.run();const runner: any = _effect.run.bind(_effect);runner.effect = _effect;return runner;
} 

单测通过:

(三)代码优化

考虑到后续options可能还会传入很多其他选项,所以进行一下重构

Object.assign(_effect, options); 

感觉语义化稍弱,所以,就抽离出一个extend方法,又考虑到这个方法可以抽离成一个工具函数,所以在src下建立shared目录,然后建立index.ts,专门放置各个模块通用的工具函数。

// src/shared/index.ts

export const extend = Object.assign;

extend(_effect, options); 

当然,重构完以后,别忘了重新跑一下effect单测。

(四)解决问题的思路

可以看到effect的单测是通过的,那完成这一组功能后,继续完成的跑一下所有单测,看看是否对其他功能造成影响。

yarn test 

果然,不出意外的话,出现意外了。

可以看到是reactivehappy path单测出了问题,而且activeEffect是个undefined,那我们回去重新看一下。

不难看出observed.foo也是触发了get操作,也就是触发了track去收集依赖,而此时并没有effect 包裹着的依赖存在,所以run不会执行,也就没有activeEffect,所以此时我们并不应该去收集依赖,所以增加一个判断。

if (!activeEffect) return;

dep.add(activeEffect);
activeEffect.deps.push(dep); 

为了验证结果,再次跑一下全部的单测。

最后

最近还整理一份JavaScript与ES的笔记,一共25个重要的知识点,对每个知识点都进行了讲解和分析。能帮你快速掌握JavaScript与ES的相关知识,提升工作效率。



有需要的小伙伴,可以点击下方卡片领取,无偿分享

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

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

相关文章

zookeeper学习笔记1(小D课堂)

win和linux双环境安装zookeeper 我们不点击这个download。 进行解压: 我们进入到conf目录。 我们给这个文件进行重命名。 接下来我们去打开它,去修改一下配置: 我们先去掉这部分注释。 去掉注释,一共就这些内容。 tickTime是我们的…

如何开通阿里云语音通知服务?

阿里云语音服务是阿里云为用户提供的一种通信服务的能力。支持快速发送语音通知服务。 安全级别更高,难窃取。支持大容量、高并发,稳定可靠。 一、如何开通阿里云语音服务? 注册阿里云平台账户;实名登记认证;阿里云语…

手把手教你 如何利用github搭建个人网站 无需服务器

目录 前言 准备工作 教程来啦!!! 第一部分 第二部分 链接说明 前言 哈喽,大家好,我是木易巷。 今天给大家分享一下:如何使用GitHub创建自己的个人网站? 准备工作 需要使用GitHub,当…

【python】语法分析-化学分子式解析「编译原理」

题目 编写程序,计算化学分子式中元素的数目,并完成以下测试: atom_count(“He”) 1 atom_count(“H2”) 2 atom_count(“H2SO4”) 7 atom_count(“CH3COOH”) 8 atom_count(“NaCl”) 2 atom_count(“C60H60”) 120 参考语法 specie…

学习周报-20221230

文章目录一 如何设置字符集二 NFS配置文件父目录权限影响子目录三 对IP分组可以批量管理NFS客户端首先查看系统环境 [rootnfs-server ~]# cat /etc/redhat-release Red Hat Enterprise Linux release 8.7 (Ootpa) [rootnfs-server ~]# uname -r 4.18.0-425.3.1.el8.x86_64这是…

JAVA零基础小白学习免费教程day13-Collection数据结构

day13_JAVAOOP 课程目标 1. 【理解】集合的体系结构 2. 【掌握】Collection集合中常用的方法 3. 【理解】Iterator迭代器 4. 【掌握】增强for的使用 5. 【理解】List集合的特点 6. 【掌握】List集合中特有的方法 7. 【理解】LinkedList集合的特点 8. 【理解】LinkedList集合中…

解读YOLO v7的代码(二)训练数据的准备

在上一篇文章解读YOLO v7的代码(一)模型结构研究_gzroy的博客-CSDN博客,我对Yolo v7的模型结构进行了分析,那么这次我们将进一步研读代码的关键部分,学习是如何对模型进行训练的。 训练数据的准备是模型训练的关键,通常我们需要对…

保姆教程系列一、什么?Redis部署 so easy

系列文章目录 !!!是的没错,胖友们,保姆教程系列又更新了!!! 保姆教程系列一、Redis部署 so easy 保姆教程系列二、Redis高可用(主从同步哨兵模式) 保姆教程系…

(3)Qt中的变体数据类型(QVariant)

QVariant的使用 QVariant(变体数据类型)这个类很神奇,或者说方便。很多时候,需要几种不同的数据类型需要传递,如果用结构体,又不大方便,容器保存的也只是一种数据类型,而QVariant则可以统统搞定。QVariant …

ceph-mds文件系统操作指南

前言:ceph-mds文件系统操作,常规操作汇总,看这一篇就够了 一、文件系统简介 Ceph 文件系统 (CephFS) 是兼容 POSIX 标准的文件系统,在 Ceph 的分布式对象存储基础上构建,称为 RADOS(可靠的自主分布式对象存…

用户级线程和内核级线程

线程的实现可以分为两类:用户级线程和内核级线程,后者又称为内核支持的线程或轻量级进程。在多线程操作系统中,各个系统的实现方式并不相同,在有的系统中实现了用户级线程,有的系统中实现了内核级线程。 用户级线程&am…

Python数据分析案例13——文本特征抽取(TfidfVectorizer)

在做机器学习的时候,构建特征变量有很多时候都是文本型的,比如电影分类的时候的电影标题,房价预测的时候房子地址,股吧评论等......都是文本类型的数据。 文本型数据怎么构建特征,它又不是分类变量不能直接独立热编码…

【再学Tensorflow2】TensorFlow2的模型训练组件(2)

TensorFlow2的模型训练组件(2)损失函数损失函数和正则化项Tensorflow2内置的损失函数自定义损失函数评估指标常用的内置评估指标自定义评估指标优化器优化器的使用使用optimizer.apply_gradients使用optimizer.minimize使用model.fitTensorflow2内置的优…

【nowcoder】笔试强训Day14

目录 一、选择题 二、编程题 2.1计算日期到天数转换 2.2幸运的袋子 一、选择题 1.定义学生、教师和课程的关系模式 S (S#,Sn,Sd,Dc,SA )(其属性分别为学号、姓名、所在系、所在系的系主任、年龄); C ( C#,Cn,P# &…

ansible的安装以及实例

目录 ansible的安装: 一、配置centos8基本源 二、配置epel 三、安装ansible 四、查看ansible是否安装以及版本 实例: 实例一:控制主机和受控主机通过root用户免密验证远程控制主机实施对应任务 实例二:控制主机连接受控主机…

SSH协议理论讲解

目录 基本概念 SSH协议的组成 SSH工作原理 SSH版本协商阶段(确定V1版本或V2版本) 算法协商阶段 密钥交换阶段 用户认证阶段 会话交互阶段 基本概念 SSH(Secure Shell)安全外壳协议,是一种用于在不安全网络上进…

Redisson实现延迟队列

k8s部署单点Redis (1)k8s部署redis的yaml文件 apiVersion: apps/v1 kind: Deployment metadata:creationTimestamp: nulllabels:app: redisname: redis spec:replicas: 1selector:matchLabels:app: redisstrategy: {}template:metadata:creationTimestamp: nulllabels:app: r…

Jmeter系统学习

Jmeter体系结构 Jmeter概念: 元件:每一个功能,例如Http请求,响应断言等。 组件:每一类元件的组合,例如采样器,配置元件。 Jmeter体系可以分为3个维度: X1--X5:负载模…

(二十八)Vue之组件化编码流程

文章目录组件化编码流程拆分静态组件实现动态组件实现交互实现添加实现勾选实现删除实现全选与全不选实现清除已完成任务TodoList案例小细节Vue学习目录上一篇:(二十七)Vue组件的样式 先看一个需求:TodoList案例 功能&#xff1a…

c# http请求使用multipart/form-data 方式上传文件及其他参数

这次的需求是请求java那边的一个excel批量上传的接口。但是他们的接口要求是这样的 于是自己写了个方法: 调用: 控制器层 var file this.HttpContext.Request.Files[0];//获取前端传来的文件 var fileName file.FileName; //注意&…