vue3响应式实现原理(2)

执行调度

当触发trigger副作用函数重新执行时,能够决定副作用函数执行的时机、次数等。如何处理:给effect再添加一个参数,提前说明他是一个对象,因为以后还会包含其他选项。判断注册副作用函数时是否存在调度器,如果存在,则直接调用调度器函数,并把当前要注册副作用函数作为参数传递过去,由用户自己控制如何执行;否则直接执行副作用函数。

现在要改变下面执行的顺序,把123放在中间执行即23之前执行。我们可以借用调度器来完成。

js 复制代码
effect(
  () => {
    console.log(proxyData.age);
  },
);
proxyData.age++;
console.log('123')
//22
//23
//123
js 复制代码
function effect(fn, options) {
  const effectFn = () => {
    activeEffect = effectFn;
    effectStack.push(activeEffect);
    fn();
    activeEffect = "";
  };
  effectFn.deps = [];
  effectFn.options = options;//新增
  effectFn();
}

  set(target, key, newVal) {
    target[key] = newVal;
    let effects = bucket?.get(target)?.get(key);
    const effectsToRun = new Set(effects);
    effects &&
      effectsToRun.forEach((effectFn) => {
        let deps = effectFn.deps;
        deps.forEach((item) => {
          item.delete(effectFn);
        });
        effectFn.deps.length = 0;
        if (activeEffect !== effectFn) {
          effectFn.options.scheduler ? effectFn.options.scheduler(effectFn) : effectFn();//新增
        }
      });
  },
  
  //接下来只需给effect函数添加第二个参数
  effect(
  () => {
    console.log(proxyData.age);
  },
  {
      scheduler(fn) {
          setTimeout(fn)
    },
  }
);

接下来看下面这种情况 会打印两次age,但是这两次操作相同,我们只关心结果,忽略过程。这个功能有点类似于在 Vue.js 中连续多次修改响应式数据但只会触发一次更新,实际上 Vue.js 内部实现了一个更加完善的调度器

js 复制代码
effect(() => {
  console.log(proxyData.age);
});
proxyData.age++
proxyData.age++

首先我们需要一个微任务队列,还需要一个任务队列(Set),以及一个标识变量。思路是按照上面的情况会同步执行两次scheduler,在scheduler中将函数添加到任务队列,因为Set的去重效果,任务队列里只会添加一个函数,再执行微任务队列,我们还需要一个标识位用来判断微任务是否执行完,因为两次同步执行scheduler会执行两次微任务队列。

js 复制代码
let jobQueue = new Set();
let p = Promise.resolve();
let flag = false;
function flushJob() {
  if (flag) return;//微任务队列未完成直接退出
  flag = true;
  p.then(() => {
    jobQueue.forEach((job) => {
      job();//执行副作用,因为set去重只会有一条打印信息,即最新的信息
    });
  }).finally(() => {
    flag = false;//修改标识代表微任务队列完成
  });
}


effect(
  () => {
    console.log(proxyData.age);
  },
  {
    scheduler(fn) {
      jobQueue.add(fn);//添加副作用
      flushJob();//刷新微任务队列
    },
  }
);

计算属性

计算属性是一个返回值,先看vue3计算属性使用方法

js 复制代码
const fullName = computed(() => { return name.value + ' Doe'; });

要实现计算属性,首先要知道只有要使用计算属性的值时才需要触发副作用函数,即我们需要将副作用存储起来。

js 复制代码
function effect(fn, options = {}) {
  const effectFn = () => {
    activeEffect = effectFn;
    effectStack.push(activeEffect);
    fn();
    activeEffect = "";
    return fn();//新增
  };
  effectFn.deps = [];
  effectFn.options = options;
  if (options.lazy) {//新增
    return effectFn;
    //return effectFn();不要直接返回一个值,这样后面不会触发副作用
  } else {
    effectFn();
  }
}


function computed(getter) {//新增
    // 把 getter 作为副作用函数,创建一个 lazy 的 effect
    const effectFn = effect(getter, { lazy: true })
    const obj = {
      get value() {
        return effectFn()
      }
    }
    return obj
  }
const sumRes = computed(() => {
  console.log("changed");
  return proxyData.age;
});
console.log(sumRes.value);
console.log(sumRes.value);

在上面打印两次sumRes.value,会触发两次副作用函数执行,显然这是没有必要的,所以我们需要把值缓存下来

js 复制代码
function computed(getter) {
  let dirty = true;
  let value;
  const effectFn = effect(getter, {
    lazy: true,
    scheduler() {//新增,当依赖改变会执行这个副作用函数
      dirty = true;
    },
  });
  const obj = {
    get value() {
      if (dirty) {//新增
        value = effectFn();
        dirty = false;
      }
      return value;
    },
  };
  return obj;
}

sumRes 是一个计算属性,并且在另一个 effect 的副作用函数中读取了 sumRes.value 的值。如果此时修改 obj.foo 的值,我们期望副作用函数重新执行

js 复制代码
const sumRes = computed(() => {
  return proxyData.age;
});
effect(() => {
  console.log(sumRes.value);
});
proxyData.age++;
proxyData.age++;

解决方法:当读取计算属性的值时,我们可以手动调用 track 函数进行追踪;当计算属性依赖的响应式数据发生变化时,我们可以手动调用 trigger 函数触发响应:

js 复制代码
function computed(getter) {
  let dirty = true;
  let value;
  const effectFn = effect(getter, {
    lazy: true,
    scheduler() {
      dirty = true;
      trigger(obj, "value");//新增
    },
  });
  const obj = {
    get value() {
      track(obj, "value");//新增
      if (dirty) {
        value = effectFn();
        dirty = false;
      }

      return value;
    },
  };
  return obj;
}

综上计算属性是由effect加上其中的lazy参数,调度函数组合实现的

相关推荐
醉城夜风~3 分钟前
CSS元素显示模式(display)
前端·css
AI大模型-小华1 小时前
Codex 三方充值快速入门指南
java·前端·数据库·chatgpt·ai编程·codex·chatgpt pro
做前端的娜娜子3 小时前
同一链接实现 PC Web 与移动 H5 自适应
前端·掘金·金石计划
小帅不太帅3 小时前
架构没变、规模没变,DeepSeek V4 Flash 正式版凭什么暴涨 47 分?
前端·aigc·deepseek
jarvisuni4 小时前
DeepSeekFlash前端依旧拉垮,而且变慢了很多!
前端·javascript·算法
卷福同学5 小时前
AI编程出海第二步:验证关键词能否做站
前端·人工智能·后端
赵庆明老师5 小时前
Vben精讲:21-详解web-antd:tsconfig.json
前端·json·vim
wc885 小时前
微软EDGE浏览器功能学习
前端·学习·edge
Csvn6 小时前
🎯 原生 `<dialog>` 元素:终于可以扔掉一半的自定义弹窗组件了?
前端