广告
返回顶部
首页 > 资讯 > 前端开发 > JavaScript >vue3调度器effect的scheduler功能实现详解
  • 434
分享到

vue3调度器effect的scheduler功能实现详解

vue3调度器effect schedulereffect scheduler 2022-12-08 20:12:42 434人浏览 独家记忆
摘要

目录一、调度执行二、单元测试三、代码实现四、回归实现五、结语一、调度执行 说到scheduler,也就是vue3的调度器,可能大家还不是特别明白调度器的是什么,先大概介绍一下。 可

一、调度执行

说到scheduler,也就是vue3的调度器,可能大家还不是特别明白调度器的是什么,先大概介绍一下。

可调度性是响应式系统非常重要的特性。首先我们要明确什么是可调度性。所谓可调度性,指的是当trigger 动作触发副作用函数重新执行时,有能力决定副作用函数执行的时机、次数以及方式。

有了调度函数,我们在trigger函数中触发副作用函数重新执行时,就可以直接调用用户传递的调度器函数,从而把控制权交给用户。

举个栗子?:

const obj = Reactive({ foo: 1 });

effect(() => {
  console.log(obj.foo);
})

obj.foo++;
obj.foo++;

首先在副作用函数中打印obj.foo的值,接着连续对其执行两次自增操作,输出如下:

   1
   2
   3

由输出结果可知,obj.foo的值一定会从1自增到3,2只是它的过渡状态。如果我们只关心最终结果而不关心过程,那么执行三次打印操作是多余的,我们期望的打印结果是:

   1
   3

那么就考虑传入调度器函数去帮助我们实现此功能,那由此需求,我们先来实现一下scheduler功能。

二、单元测试

首先还是藉由单测来梳理一下功能,这是直接从Vue3源码中粘贴过来对scheduler的单测,里面很详细的描述了scheduler的功能。

it('scheduler', () => {
  let dummy;
  let run: any;
  const scheduler = jest.fn(() => {
    run = runner;
  });
  const obj = reactive({ foo: 1 });
  const runner = effect(
    () => {
      dummy = obj.foo;
    },
    { scheduler },
  );
  expect(scheduler).not.toHaveBeenCalled();
  expect(dummy).toBe(1);
  // should be called on first trigger
  obj.foo++;
  expect(scheduler).toHaveBeenCalledTimes(1);
  // should not run yet
  expect(dummy).toBe(1);
  // manually run
  run();
  // should have run
  expect(dummy).toBe(2);
});

大概介绍一下这个单测的流程:

  • 通过 effect 的第二个参数给定的一个对象 { scheduler: () => {} }, 属性是scheduler, 值是一个函数;
  • effect 第一次执行的时候, 还是会执行 fn;
  • 当响应式对象被 set,也就是数据 update 时, 如果 scheduler 存在, 则不会执行 fn, 而是执行 scheduler;
  • 当再次执行 runner 的时候, 才会再次的执行 fn.

三、代码实现

那接下来就直接开始代码实现功能,这里直接贴出完整代码了,// + 会标注出新增加的代码。

class ReactiveEffect {
  private _fn: any;

  // + 接收scheduler
  // + 在构造函数的参数上使用public等同于创建了同名的成员变量
  constructor(fn, public scheduler?) {
    this._fn = fn;
  }

  run() {
    activeEffect = this;
    return this._fn();
  }
}

// * ============================== ↓ 依赖收集 track ↓ ============================== * //
// * targetMap: target -> key
const targetMap = new WeakMap();

// * target -> key -> dep
export function track(target, key) {
  // * depsMap: key -> dep
  let depsMap = targetMap.get(target);
  if (!depsMap) {
    targetMap.set(target, (depsMap = new Map()));
  }

  // * dep
  let dep = depsMap.get(key);
  if (!dep) {
    depsMap.set(key, (dep = new Set()));
  }

  dep.add(activeEffect);
}

// * ============================== ↓ 触发依赖 trigger ↓ ============================== * //
export function trigger(target, key) {
  let depsMap = targetMap.get(target);
  let dep = depsMap.get(key);

  for (const effect of dep) {
    // + 判断是否有scheduler, 有则执行,无则执行fn
    if (effect.scheduler) {
      effect.scheduler();
    } else {
      effect.run();
    }
  }
}

let activeEffect;

export function effect(fn, options: any = {}) {
  // + 直接将scheduler挂载到依赖上
  const _effect = new ReactiveEffect(fn, options.scheduler);

  _effect.run();

  return _effect.run.bind(_effect);
}

代码实现完成,那接下来看一下单测结果。

四、回归实现

好,现在我们再回到最初的栗子?,在上面scheduler基础上,完成现有需求,继续看一下对此需求的单测。

it('job queue', () => {
  // 定义一个任务队列
  const jobQueue = new Set();
  // 使用 Promise.resolve() 创建一个 Promise 实例,我们用它将一个任务添加到微任务队列
  const p = Promise.resolve();

  // 一个标志代表是否正在刷新队列
  let isFlushing = false;

  function flushJob() {
    // 如果队列正在刷新,则什么都不做
    if (isFlushing) return;
    // 设置为true,代表正在刷新
    isFlushing = true;
    // 在微任务队列中刷新 jobQueue 队列
    p.then(() => {
      jobQueue.forEach((job: any) => job());
    }).finally(() => {
      // 结束后重置 isFlushing
      isFlushing = false;
      // 虽然scheduler执行两次,但是由于是Set,所以只有一项
      expect(jobQueue.size).toBe(1);
      // 期望最终结果拿数组存储后进行断言
      expect(logArr).toEqual([1, 3]);
    });
  }

  const obj = reactive({ foo: 1 });
  let logArr: number[] = [];

  effect(
    () => {
      logArr.push(obj.foo);
    },
    {
      scheduler(fn) {
        // 每次调度时,将副作用函数添加到 jobQueue 队列中
        jobQueue.add(fn);
        // 调用 flushJob 刷新队列
        flushJob();
      },
    },
  );

  obj.foo++;
  obj.foo++;

  expect(obj.foo).toBe(3);
});

在分析上段代码之前,为了辅助完成上述功能,我们需要回到trigger中,调整一下遍历执行,为了让我们的scheduler能拿到原始依赖。

for (const effect of dep) {
  // + 判断是否有scheduler, 有则执行,无则执行fn
  if (effect.scheduler) {
    effect.scheduler(effect._fn);
  } else {
    effect.run();
  }
}

再观察上面的单测代码,首先,我们定义了一个任务队列jobQueue,它是一个Set数据结构,目的是利用Set数据结构的自动去重功能。

接着我们看调度器scheduler的实现,在每次调度执行时,先将当前副作用函数添加到jobQueue队列中,再调用flushJob函数刷新队列。

然后我们把目光转向flushJob函数,该函数通过isFlushing标志判断是否需要执行,只有当其为false 时才需要执行,而一旦flushJob函数开始执行,isFlushing标志就会设置为true,意思是无论调用多少次flushJob函数,在一个周期内都只会执行一次。

需要注意的是,在flushJob内通过p.then将一个函数添加到微任务队列,在微任务队列内完成对jobQueue的遍历执行。

整段代码的效果是,连续对obj.foo执行两次自增操作,会同步且连续地执行两次scheduler调度函数,这意味着同一个副作用函数会被jobQueue.add(fn)添加两次,但由于Set数据结构的去重能力,最终jobQueue中只会有一项,即当前副作用函数。

类似地,flushJob也会同步且连续执行两次,但由于isFlushing标志的存在,实际上flushJob函数在一个事件循环内只会执行一次,即在微任务队列内执行一次。

当微任务队列开始执行时,就会遍历jobQueue并执行里面存储的副作用函数。由于此时jobQueue队列内只有一个副作用函数,所以只会执行一次,并且当它执行时,字段obj.foo的值已经是3了,这样我们就实现了期望的输出。

再跑一遍完整流程,来看一下单测结果,确保新增代码不影响以往功能。

测试结束完以后,由于job queue是一个实际案例单测,所以我们将其抽离到examples下面的testCase里,建立jobQueue.spec.ts

五、结语

可能你已经注意到了,这个功能点类似于在vue.js中连续多次修改响应式数据但只会触发一次更新,实际上Vue.js内部实现了一个更加完善的调度器,思路与上文介绍的相同。

此外,综合前面的这些内容,我们就可以实现Vue.js中一个非常重要且非常有特色的能力:computed计算属性,这个就后面再慢慢实现吧...

以上就是vue3调度器effect的scheduler功能实现详解的详细内容,更多关于vue3调度器effect scheduler的资料请关注编程网其它相关文章!

--结束END--

本文标题: vue3调度器effect的scheduler功能实现详解

本文链接: https://www.lsjlt.com/news/174426.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • vue3调度器effect的scheduler功能实现详解
    目录一、调度执行二、单元测试三、代码实现四、回归实现五、结语一、调度执行 说到scheduler,也就是vue3的调度器,可能大家还不是特别明白调度器的是什么,先大概介绍一下。 可...
    99+
    2022-12-08
    vue3调度器effect scheduler effect scheduler
  • vue3调度器effect的scheduler功能怎么实现
    本文小编为大家详细介绍“vue3调度器effect的scheduler功能怎么实现”,内容详细,步骤清晰,细节处理妥当,希望这篇“vue3调度器effect的scheduler功能怎么实现”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入...
    99+
    2023-07-04
  • 一文理解Goland协程调度器scheduler的实现
    目录1. 调度器scheduler的作用2. GMP模型3. 调度机制1. 调度器scheduler的作用 我们都知道,在Go语言中,程序运行的最小单元是gorouines。 然而程...
    99+
    2022-11-13
  • Django实现带进度条的倒计时功能详解
    目录前期准备倒计时部分完整代码嵌入式倒计时器进度条部分环形进度条插件前期准备 前端框架 你需要准备一些前端框架:Bootstrap4 和 jQuery安装方法请自...
    99+
    2023-05-15
    Django实现带进度条的倒计时功能 Django带进度条的倒计时 Django 倒计时 Django 进度条
  • Qt实现小功能之圆形进度条的方法详解
    目录功能图形绘制1.绘制窗口整体背景色值2.圆形进度条通道绘制3.圆形进度条绘制4.文本绘制数值计算1.计算步长2.实时数据计算在Qt自带的控件中,只有垂直进度条、水平进度条两种。 ...
    99+
    2022-11-13
  • vue3使用自定义指令实现eldialog拖拽功能示例详解
    目录实现el-dialog的拖拽功能通过自定义指令实现拖拽功能实现拖拽功能使用方式实现el-dialog的拖拽功能 这里指的是 element-plus 的el-dialog组件,一...
    99+
    2022-11-13
  • 阿里云服务器实现的功能详解
    阿里云服务器是阿里云推出的一种云服务器产品,可以提供稳定的计算、存储和网络服务,广泛应用于企业级应用、云计算、大数据分析、人工智能等领域。本文将详细介绍阿里云服务器能够实现的各种功能。 阿里云服务器是阿里云推出的一种云服务器产品,可以提供稳...
    99+
    2023-11-20
    阿里 详解 功能
  • SpringBoot实现功能的统一详解
    目录1. 统一用户登录权限验证1.1 自定义拦截器1.2 将自定义拦截器加入到系统配置1.3 运行结果1.4 总结2. 统一异常处理2.1 代码实现2.2 运行结果3. 统一数据返回...
    99+
    2022-11-13
  • javascript实现计算器功能详解流程
    目录1、计算器功能介绍2、计算器页面设计1、导航栏部分2、数据部分3、index.wxml布局页面4、index.css样式页面5、运行结果3、功能实现部分1、删除功能2、清空功能3...
    99+
    2022-11-12
  • Python实现多功能音乐播放器详解
    目录前言准备工作知识点和所需模块环境完整代码效果展示导入模块界面按钮功能创建一个文件目录音乐读取功能显示已加载的音乐播放音乐停止播放下一首上一首音量控制关闭窗口前言 就是用Pytho...
    99+
    2022-11-13
  • Springboot发送邮件功能的实现详解
    目录前言成果展示表设计引入依赖邮箱工具类mapperXmlServiceimplEmailServiceImpl写完后要去进行配置获取授权码总结前言 大多数小伙伴在练习与学习的过程中...
    99+
    2022-11-13
  • Vue登录功能的实现流程详解
    目录Vue项目中实现登录大致思路安装插件创建store封装axiosqs vue 插件api.js的作用路由拦截登录页面实际使用Vue项目中实现登录大致思路 1、第一次登录的时候,前...
    99+
    2022-11-13
  • 详解php团购功能的实现原理
    随着电子商务的迅猛发展,网购已经成为我们生活中不可或缺的一部分。团购作为其中一种形式,也被越来越多的人所接受。在团购的背后,有着一些技术实现的原理。本文将介绍php团购功能实现原理。一、什么是团购团购就是一种商品或服务的销售方式,由一家或多...
    99+
    2023-05-14
  • C++调用libcurl开源库实现邮件的发送功能流程详解
    目录1、为啥要选择libcurl库去实现邮件的发送2、调用libcurl库的API接口实现邮件发送3、构造待发送的邮件内容4、开通163发送邮件账号的SMTP服务5、排查接收的邮件内...
    99+
    2022-11-12
  • APT 注解处理器实现 Lombok 常用注解功能详解
    目录1 背景2 生成字节码原理2.1 APT(Annotation Processing Tool )注解处理器2.2 AbstractProcessor 注解处理器的使用...
    99+
    2022-11-13
  • C#怎么实现调用浏览器的功能
    本篇内容主要讲解“C#怎么实现调用浏览器的功能”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“C#怎么实现调用浏览器的功能”吧!C#调用浏览器之调用IE:System.Diagnostics.Pr...
    99+
    2023-06-17
  • Qt实现模糊匹配功能的实例详解
    目录1.准备基础数据2.创建并实例化匹配类3.控件绑定对于浏览器的使用,我想大家一定不会陌生吧,输入要搜索的内容时,会出现相应的匹配信息。 那么,今天我要讲述的也是这样一个功能。 首...
    99+
    2022-11-13
    Qt模糊匹配功能 Qt模糊匹配
  • MyBatisPlus+Lombok实现分页功能的方法详解
    目录一、Lombok1、添加Lombok依赖2、安装Lombok插件3、模型类上添加注解二、分页功能1、调用方法传入参数获取返回值2、设置分页拦截器3、运行测试程序一、Lombok ...
    99+
    2022-11-13
  • SpringBoot实现WEB的常用功能案例详解
    目录前言SpringMVC整合支持Spring MVC自动配置项目基础环境搭建功能扩展实现Spring MVC功能扩展实现Spring整合Servlet三大组件使用注册方式整合使用组...
    99+
    2022-11-13
  • 基于SpringBoot和Vue3的博客平台文章详情与评论功能实现
    目录1. 后端Spring Boot实现1.1 创建Comment实体类1.2 创建CommentMapper接口1.3 创建CommentService接口及实现1.4 创建Com...
    99+
    2023-05-15
    基于SpringBoot和Vue3文章查看 基于Spring Boot和Vue3文章评论
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作