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

简介

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

《实现vue3响应式系统核心》 系列文章

代码地址: github.com/SuYxh/share...

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

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

调度执行

场景

js 复制代码
const obj = reactive({ foo: 1 })

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

obj.foo ++

console.log('结束了');

当前代码执行顺序:

1
2
结束了

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

1
结束了
2

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

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

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

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

编写单元测试

js 复制代码
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
});

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

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

obj.foo++;

console.log('结束了');

代码实现

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

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

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

js 复制代码
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()
    }
  });
}

运行单测

执行 test

之前的 case 也没有问题!

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

流程图

整体流程图:

相关推荐
程序员海军9 分钟前
2024 Nuxt3 年度生态总结
前端·nuxt.js
m0_7482567819 分钟前
SpringBoot 依赖之Spring Web
前端·spring boot·spring
web135085886351 小时前
前端node.js
前端·node.js·vim
m0_512744641 小时前
极客大挑战2024-web-wp(详细)
android·前端
若川1 小时前
Taro 源码揭秘:10. Taro 到底是怎样转换成小程序文件的?
前端·javascript·react.js
潜意识起点1 小时前
精通 CSS 阴影效果:从基础到高级应用
前端·css
奋斗吧程序媛1 小时前
删除VSCode上 origin/分支名,但GitLab上实际上不存在的分支
前端·vscode
IT女孩儿1 小时前
JavaScript--WebAPI查缺补漏(二)
开发语言·前端·javascript·html·ecmascript
m0_748256564 小时前
如何解决前端发送数据到后端为空的问题
前端
请叫我飞哥@4 小时前
HTML5适配手机
前端·html·html5