vue3响应式实现原理(3)

70 阅读2分钟

纠正两个问题

function trigger(target, key) {
  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;
      //结尾
      //这一段代码应放到执行effectFn函数之前,如果放在这,后面的代码不需要执行,effectFn函数也不会触发,所以需要把这段代码换个位置
      if (activeEffect !== effectFn) {
        effectFn.options.scheduler
          ? effectFn.options.scheduler(effectFn)
          : effectFn();
      }
    });
}
//放到effect函数内
function effect(fn, options = {}) {
  const effectFn = () => {
    let deps = effectFn.deps;
    deps.forEach((item) => {
      item.delete(effectFn);
    });
    effectFn.deps.length = 0;
    activeEffect = effectFn;
    effectStack.push(activeEffect);
    res=fn();//也就是以这个函数执行为分界线,在这之前清除副作用,赋值入栈,之后出栈赋值
    effectStack.pop();
    activeEffect = effectStack[effectStack.length - 1];
    //return fn();不能直接返回fn()会导致fn的立即执行
    return res//用res存下来
  };
  effectFn.deps = [];
  effectFn.options = options;
  if (options.lazy) {
    return effectFn;
  } else {
    effectFn();
  }
}


实现watch

首先说明watch的实现也需要effect加调度器,先实现一个基础的watch

function watch(obj, cb) {
  effect(() => obj.age, {
    scheduler() {
      cb();
    },
  });
}
watch(proxyData, () => {
  console.log("changed");
});
proxyData.age++;
proxyData.age++;

上面只能监听一个属性,现在需要监听传入对象的任意属性都能触发

function watch(obj, cb) {
  effect(
    () => {
      travel(obj)
    },
    {
      scheduler() {
        cb();
      },
    }
  );
}
function travel(obj){
  for (const key in obj) {
    if (typeof obj[key] === 'object' && obj[key]!=null) {
      travel(obj[key]);
    }
  }
}

watch的参数也能传入一个函数,实现也很简单,只需要简单判断执行

function watch(getter, cb) {
  effect(
    () => {
      if (typeof getter === "function") {
        getter();
      } else {
        travel(getter);
      }
    },
    {
      scheduler() {
        cb();
      },
    }
  );
}

那么怎么拿到watch的新值和旧值,可以借助之前的lazy,他保存着上次副作用函数执行的结果,也就是我们需要的旧值,并且watch不需要再执行副作用函数,所以使用lazy没有任何影响。

function watch(getter, cb) {
  let newVal, oldVal;//新增
  const effectFn = effect(
    () => {
      if (typeof getter === "function") {
        return getter();//新增,这里要加个return,把值返回回来才能用lazy拿到
      } else {
        travel(getter);
      }
    },
    {
      lazy: true,//新增
      scheduler() {
        newVal = effectFn();//新增
        cb(newVal, oldVal);
        oldVal = newVal;//新增
      },
    }
  );
  oldVal = effectFn();//新增
}

watch还有一个参数immediate,在注册的时候就应该监听一次,之前在注册watch函数后等待数据改变触发副作用,现在直接在注册watch函数手动执行一次函数。实现:给watch添加第三个参数

watch(
  () => proxyData.age,
  (newVal, oldVal) => {
    console.log(newVal, oldVal);
  },
  { immediate: true }
);

function watch(getter, cb, options = {}) {
  let newVal, oldVal;
  const job = () => {//将调度函数抽离
    newVal = effectFn();
    cb(newVal, oldVal);
    oldVal = newVal;
  };
  const effectFn = effect(
    () => {
      if (typeof getter === "function") {
        return getter();
      } else {
        travel(getter);
      }
    },
    {
      lazy: true,
      scheduler: job,//修改
    }
  );
  if (options.immediate) {//新增
    job();
  } else {
    newVal = effectFn();
  }
}

flush用于控制调度器的执行时机

在调度器函数内检测 options.flush 的值是否为post,如果是,则将 job 函数放到微任务队列中,从而实现异步延迟执行;否则直接执行 job 函数

watch(
  () => proxyData.age,
  (newVal, oldVal) => {
    console.log(newVal, oldVal);
  },
  { immediate: true, flush: "post" }
);

function watch(getter, cb, options = {}) {
  let newVal, oldVal;
  const job = () => {
    newVal = effectFn();
    cb(newVal, oldVal);
    oldVal = newVal;
  };
  const effectFn = effect(
    () => {
      if (typeof getter === "function") {
        return getter();
      } else {
        travel(getter);
      }
    },
    {
      lazy: true,
      scheduler() {//修改
        if (options.flush === "post") {
          const p = Promise.resolve();
          p.then(job);
        } else {
          job();
        }
      },
    }
  );
  if (options.immediate) {
    job();
  } else {
    newVal = effectFn();
  }
}