【Vue3源码】第二章 effect功能的完善下

news2024/9/25 11:17:07

【Vue3源码】第二章 effect功能的完善下

前言

上一章节我们实现了effect函数的runner 和 scheduler,这一章我们继续完善effect函数的功能,stop和onstop。

1、实现effect的stop功能

顾名思义,stop就是让effect停下来的函数。那么怎么才能让effect停下呢?很简单,我们触发run方法时,清空targetMap(前几章中我把它比作仓库)中的dep(收集到的ReactiveEffect),那么run方法调用时由于dep中没有ReactiveEffect,自然就无法触发依赖实现响应式啦~

如果你忘了dep是什么看看这张图帮你回忆起来

在这里插入图片描述

先看下我们effect.spec.ts文件中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
  	// 响应式停止了,为什么没有变化呢?我们来实现stop功能!
    expect(dummy).toBe(2)

    //stopped effect should still be manually callable
    runner()
    expect(dummy).toBe(3)

  })

根据上文中测试代码,我们开始实现这个多出来的stop函数,它接收的参数是effect的返回值:runner

  1. 第一步我们先从修改effect函数。
//响应式函数
export const effect = (fn, options: any = {}) => {
  const _effect = new ReactiveEffect(fn, options.scheduler);
  _effect.run();
  // 给runner一个any类型逃脱类型检查(本文不做类型上的考虑)
  const runner :any = _effect.run.bind(_effect);
  // 给runner添加一个属性属性effect
  runner.effect = _effect
  return runner;
};

上面的代码很巧妙的在runner里添加了一个ReactiveEffect实例(_effect),我们的stop函数要接收effect的返回值runner作为参数,而runner又可以把ReactiveEffect实例带进自己的effect属性中。

为什么要这么带呢?

因为runner携带上effect实例作为属性后,就可以在runner中调用ReactiveEffect类中的方法了!

怎么实现呢?

我们接下来就要改造ReactiveEffect类了,写一个stop方法,这样我们就可以在stop函数中通过runner.effect.stop去调用ReactiveEffect类写好的stop方法。

注意stop方法属于ReactiveEffect类,stop函数是独立的函数不要搞混了哦~看下面我们马上修改ReactiveEffect类

  1. 修改track函数实现反向收集

要实现stop方法和stop函数前,我们需要收集到dep依赖(之前的文字中我把它比作二级分类),然后清空dep(二级分类存放的是最终的货物)收集到的依赖。~

vue3源码又很巧妙的在ReactiveEffect中新增了deps属性,即activeEffect(全局变量)中添加了一个deps属性(下面修改ReactiveEffect类会写)

runner新增了一个effect属性巧妙,activeEffect新增了一个deps属性,他们都巧妙的串联了起来

看到这我不禁感叹:优雅!实在是太优雅了~

let activeEffect; // 现在它又多了个deps属性


//依赖收集
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);
  }
  dep.add(activeEffect);
  //浅拷贝反向收集到dep
  activeEffect.deps.push(dep)
}
  1. 修改ReactiveEffect类实现stop方法和stop函数

好了dep都被收集到了activeEffect.deps中,runner也可以通过runner.effect.stop调用stop方法了

那么stop方法和stop函数就可以闪亮登场了~

class ReactiveEffect {
  private _fn;
  deps = []//新增deps数组
  constructor(fn, public scheduler?) {
    this._fn = fn;
    this.scheduler = scheduler;
  }

  run() {
    activeEffect = this; //我们会把实例收集到activeEffect中,所以activeEffect中会有deps属性
    return this._fn();
  }
  stop() {
    // 遍历收集到的deps
    this.deps.forEach((dep:any) => {
      //因为是浅拷贝收集到的dep,所以这里删掉对应的dep就没有了,没有dep(二级分类)自然就无法触发run方法!
      dep.delete(this)
    });
  }
}

stop函数

很简单,去触发实例中的stop方法即可~

// 停止函数
export const stop = (runner) => {
  runner.effect.stop()
};

非常的优雅,代码还可以继续优化~

试想一下,我们第一次触发stop函数后是不是已经清空了dep中所有收集到的ReactiveEffect类?

那么我们继续调用stop函数,还有必要继续清空一个空的dep吗??显然是不用的。

为了代码可读性,我们还可以把stop方法中删掉dep的操作抽离成一个函数clearupEffect增加代码可读性

class ReactiveEffect {
  private _fn;
  active = true; //新增一个变量控制dep是否需要清空
  deps = [];
  constructor(fn, public scheduler?) {
    this._fn = fn;
    this.scheduler = scheduler;
  }

  run() {
    activeEffect = this;
    return this._fn();
  }
  stop() {
    //第一次可以触发
    if(this.active) {
			// 抽离成clearupEffect
      clearupEffect(this);
      //清空后再次stop就无法操作了,只有下次触发响应式函数充值active才能继续触发依赖~
      this.active = false
    }
  }
}

//封装
function clearupEffect(effect) {
  effect.deps.forEach((dep: any) => {
    dep.delete(effect);
  });
}

2.实现onStop功能

和之前的scheduler功能一样,onStop也属于effect第二参数的属性之一,当effect传入onStop函数时,它会在stop函数触发时,跟着触发一次。

我们先看测试用例:

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

    stop(runner)
    expect(onStop).toBeCalledTimes(1)
  })
  1. 第一步修改ReactiveEffect类

    首先我们要在ReactiveEffect类中声明onStop这个属性(函数类型)

    并且在stop方法中,我们通过判断是否传了onStop这个属性来调用执行onStop函数

    实现起来非常简单

class ReactiveEffect {
  private _fn;
  active = true;
  deps = [];
  onStop? :() => void; //声明它是个函数类型
  constructor(fn, public scheduler?) {
    this._fn = fn;
    this.scheduler = scheduler;
  }

  run() {
    activeEffect = this;
    return this._fn();
  }
  stop() {
    if(this.active) {
      clearupEffect(this);
      // 判断onstop是否存在,存在就执行这个函数
      if(this.onStop) {
        this.onStop()
      }
      this.active = false
    }
  }
}

  1. 第二步修改effect函数让它能接受onStop
import { extend } from "../shared/extend";


//响应式函数
export const effect = (fn, options: any = {}) => {
  const _effect = new ReactiveEffect(fn, options.scheduler);
  //extend 相当于 Object.assign,我们把options添加到_effect属性里
  extend(_effect,options)
  _effect.run();
  const runner: any = _effect.run.bind(_effect);
  runner.effect = _effect;
  return runner;
};

vue3源码为了语意化把 Object.assign 改成了 extend 常量

我们在src下新建一个shared文件夹(分享文件夹),并新建一个extend.js文件

在这里插入图片描述

代码如下:

export const  extend = Object.assign

3.单元测试,effect流程图

在这里插入图片描述

好了effect响应式函数的主要功能就基本完成了~

effect的流程图

画这个太累了~不过希望大家会喜欢

在这里插入图片描述

下节预告《vue3源码中的readonly功能》

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

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

相关文章

系统分享|分享几个Windows系统镜像下载网站

📣今日作品:如何关闭Microsoft start方法介绍👦创作者:Jum朱⏰预计花费:10分钟📖个人主页:Jum朱博客的个人主页系统之家传送门:https://www.xitongzhijia.net/这个是老牌一直还在运营…

聊聊RocketMQ 的功能特性

这是RocketMQ的第三篇文章,前两篇文章我们说了一下rocketmq的入门安装和开发配置,以及他的一些名词解释,RocketMQ入门第一次,RocketMQ(二) 领域名词。今天我们来说说的他的一些功能特性。明确区分这些功能特…

[AI生成图片] 效果最好的Midjourney 的介绍和使用

Midjourney介绍: 是一个文本生成图片的扩散模型,能够根据输入的任何文本生成令人难以置信的图像,让数十亿人在几秒钟内创造惊人的艺术。为方便用户控制和快速生成图片,打开后在页面底部输入文本内容,稍等一小会&#…

基于easyexcel的MySQL百万级别数据的excel导出功能

前言最近我做过一个MySQL百万级别数据的excel导出功能,已经正常上线使用了。这个功能挺有意思的,里面需要注意的细节还真不少,现在拿出来跟大家分享一下,希望对你会有所帮助。原始需求:用户在UI界面上点击全部导出按钮…

全网多种方式解决The requested resource [/] is not available的错误

文章目录1. 复现错误2. 分析错误3. 解决错误3.1 本地项目3.2 线上项目4. 此错误的其他解决方法5. 补充说明1. 复现错误 曾记得,当初使用idea来写Java web项目时,常常因为Tomcat配置导致如下错误: 即The requested resource [/] is not avail…

自动化完成1000个用户的登录并获取token并生成tokens.txt文件

自动化完成1000个用户的登录并获取token并生成tokens.txt文件 写作背景 在我学习使用redis实现秒杀功能的过程中,在编写完秒杀代码后,需要使用Jmeter实际测试1000个用户进行秒杀,由于秒杀功能需要在用户登录完成后才能实现,用户是…

DFS深度优先搜索—Java版

递归三要素 递归的定义 递归的拆解 递归的出口 什么时候使用DFS? 深度回溯问题(DFS与回溯区别不大) 二叉树问题 组合、排列问题 找方案问题(解空间是一棵树或者图,需要自行构造图/树) 图的搜索问题…

Smokeping的主从模式部署

Smokeping 支持 Standalone(单机)模式和 Master/Slave(主从)模式。 之前老苏折腾过单机模式,这次应网友 Roxmie 的要求,研究了一下主从模式的部署 文章传送门: 网络性能监控工具Smokeping 因为…

Go基础-环境安装

文章目录1 Go?Golang?2 下载Go3 windows安装4 测试是否成功1 Go?Golang? Go也称为Golang,是Google开发的一个开源的编译型的静态语言。 Golang的主要关注点是高可用、高并发和高扩展性,Go语言定位是系统级编程语言,对web程序具有很好的支…

SAP数据导入工具(LSMW) 超级详细教程(批量导入内部订单)

目录 第一步:记录批导步骤编辑数据源对应字段 第二步:维护数据源 第三步:维护数据源对应字段(重要) 第四步:维护数据源关系。 第五步:维护数据源与导入字段的对应关系。 第六步&#xff0…

K_A12_006 基于STM32等单片机驱动BH1750模块 串口与OLED0.96双显示

K_A12_006 基于STM32等单片机驱动BH1750模块 串口与OLED0.96双显示一、资源说明二、基本参数参数引脚说明三、驱动说明时序对应程序:四、部分代码说明1、接线引脚定义1.1、STC89C52RCBH1750模块1.2、STM32F103C8T6BH1750模块五、基础知识学习与相关资料下载六、视频效果展示与程…

《蓝桥杯每日一题》递归·AcWing 1497. 树的遍历

1.题目描述一个二叉树,树中每个节点的权值互不相同。现在给出它的后序遍历和中序遍历,请你输出它的层序遍历。输入格式第一行包含整数 N,表示二叉树的节点数。第二行包含 N个整数,表示二叉树的后序遍历。第三行包含 N 个整数&…

设计模式之迭代器模式与命令模式详解和应用

目录1 迭代器模式1.1 目标1.2 内容定位1.3 迭代器模式1.4 迭代器模式的应用场景1.5 手写字定义的送代器1.6 迭代器模式在源码中的体现1.7 迭代器模式的优缺点2 命令模式2.1 定义2.2 命令模式的应用场景2.3 命令模式在业务场景中的应用2.4 命令模式在源码中的体现2.5 命令模式的…

UVa 211 The Domino Effect 多米诺效应 暴力搜索

题目链接:UVa 211 The Domino Effect 题目描述: 一张多米诺骨牌拥有两个数值,一共有二十八张不同的多米诺骨牌,这二十八张多米诺骨牌的点数如下图所示: 上图的BoneBoneBone代表编号,而PipsPipsPips代表两个…

Springboot扩展点系列之终结篇:Bean的生命周期

前言关于Springboot扩展点系列已经输出了13篇文章,分别梳理出了各个扩展点的功能特性、实现方式和工作原理,为什么要花这么多时间来梳理这些内容?根本原因就是这篇文章:Spring bean的生命周期。你了解Spring bean生命周期&#xf…

前端最全面试题整理

前端基础 一、 HTTP/HTML/浏览器 1、说一下 http 和 https https 的 SSL 加密是在传输层实现的。 (1) http 和 https 的基本概念 http: 超文本传输协议,是互联网上应用最为广泛的一种网络协议,是一个客户端和服务器端请求和应答的标准(T…

操作SSH无密登录配置

例如小编有三台服务器需要相互访问,就需要配置三台,这三台分别是hadoop102,hadoop103 , hadoop1041.打开三个服务器,分别生成hadoop102,hadoop103 , hadoop104的公钥和私钥输入命令,然后一直回车,这时候什么…

狂神聊Redis复习笔记二

目录事务监控! Watch (面试常问!)悲观锁:乐观锁:Redis测监视测试Redis.conf详解Redis持久化RDB(Redis DataBase)AOF(Append Only File)Redis发布订阅Redis主从…

Docker资源隔离(namespace,cgroups)

一、概述 Docker容器的本质是宿主机上的一个进程。Docker通过namespace实现了资源隔离,通过cgroups实现了资源限制,通过写时复制机制(copy-on-write)实现了高效的文件操作。 二、Linux内核的namespace机制 namespace 机制提供一种…

jfr引起的一次jvm异常记录

业务生产启动时,20个节点有1-2个节点因为jvm问题出现启动失败,k8s自动重启后正常。在测试环境2个节点下偶现 排查思路: 先拿到hs_err_pid的jvm错误文件找到当前线程和内部错误信息 hs_err_pid 文件分析 当前线程:lettuce的线程…