实现vue3响应式系统核心-scheduler

本文介绍了如何在Vue3中实现可调度的副作用函数,通过添加scheduler选项参数,控制副作用函数的执行时机。并通过单元测试展示了调度逻辑,确保功能按需调整执行顺序。
摘要由CSDN通过智能技术生成

在这里插入图片描述

简介

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

代码地址: https://github.com/SuYxh/share-vue3

代码并没有按照源码的方式去进行组织,目的是学习、实现 vue3 响应式系统的核心,用最少的代码去实现最核心的能力,减少我们的学习负担,并且所有的流程都会有配套的图片,图文 + 代码,让我们学习更加轻松、快乐。

每一个功能都会提交一个 commit ,大家可以切换查看,也顺变练习练习 git 的使用。

调度执行

场景

const obj = reactive({ foo: 1 })

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

obj.foo ++

console.log('结束了');

当前代码执行顺序:

1
2
结束了

现在假设需求有变,输出顺序需要调整为:

1
结束了
2

根据打印结果我们很容易想到对策,即把语句 obj.foo++ 和语句 console.log('结束了')位置互换即可。

那么有没有什么办法能够在不调整代码的情况下实现需求呢?这时就需要响应系统支持调度。

我们可以为 effect 函数设计一个选项参数 options,允许用户指定调度器:

effect(
  () => {
    console.log(obj.foo);
  },
  // options
  {
    // 调度器 scheduler 是一个函数
    scheduler(fn) {
      // ...
    }
  }
);

编写单元测试

it('should follow the correct order', () => {
  // 保存原始的 console.log
  const originalConsoleLog = console.log;
  // 创建一个模拟的 console.log 函数
  const mockConsoleLog = vi.fn();
  global.console.log = mockConsoleLog; // 重定向 console.log 到 mock 函数

  const obj = reactive({ foo: 1 })

  effect(() => {
    mockConsoleLog(obj.foo);
  }, {
    scheduler: function (fn) {
      // 将副作用函数放到宏任务队列中执行
      setTimeout(fn, 0);
    }
  })

  obj.foo++;

  mockConsoleLog('结束了');

  // 检查调用顺序
  expect(mockConsoleLog.mock.calls[0][0]).toBe(1); // 第一次调用,参数应该是 1
  expect(mockConsoleLog.mock.calls[1][0]).toBe('结束了'); // 第二次调用,参数应该是 '结束了'

  // 清理模拟
  mockConsoleLog.mockClear();
  global.console.log = originalConsoleLog; // 恢复 console.log
});

这个单元测试有点难,如果看不明白,当代码实现后,直接使用下面代码去运行,也是可以。

effect(
  () => {
    console.log('effect', obj.foo);
  },
  // options
  {
    // 调度器 scheduler 是一个函数
    scheduler(fn) {
      console.log('scheduler');
      // 将副作用函数放到宏任务队列中执行
      setTimeout(fn);
    }
  }
);

obj.foo++;

console.log('结束了');

代码实现

1、首先保存一下用户传入的 options ,将其挂载到 effectFn 函数上

export function effect(fn, options = {}) {
  // ...
  // 将 options 挂载到 effectFn 上
  effectFn.options = options; // 新增
  // effectFn.deps 用来存储所有与该副作用函数相关联的依赖集合
  effectFn.deps = [];
  // 执行副作用函数
  effectFn();
}

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

function trigger(target, key) {
  const depsMap = bucket.get(target);
  if (!depsMap) return;
  const effects = depsMap.get(key);

  const effectsToRun = new Set();
  effects && effects.forEach(effectFn => {
    // 如果 trigger 触发执行的副作用函数与当前正在执行的副作用函数相同,则不触发执行
    if (effectFn !== activeEffect) { 
      effectsToRun.add(effectFn);
    }
  });
  effectsToRun.forEach(effectFn => {
    if (effectFn.options.scheduler) {
      effectFn.options.scheduler(effectFn)
    } else {
      effectFn()
    }
  });
}

运行单测

image-20240118155004753

执行 test

image-20240118155042555

之前的 case 也没有问题!

相关代码在 commit: (997ecd0)实现调度执行 ,git checkout 997ecd0 即可查看。

流程图

整体流程图:

image-20240118154336393

引导扫码关注

一个前端小学生的学习之路,如果你喜欢前端,我们可以一起进行学习、交流、共建。可以添加好友,结伴学习,成长的路上不孤单!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值