Vue3 watch & watchEffect 手写源码

110 阅读3分钟

禁止转载,侵权必究!

Watch 实现原理

watch 的核心就是观测一个响应式数据,当数据变化时通知并执行回调 (那也就是说它本身就是一个 effect)

1.监测响应式对象

3.watch.html script

const state = reactive({ flag: false, name: "lx", age: 18, n: { n: 1 } });
// 数据变化后就调用回调方法
// 常见的两种方式  直接监控某个对象  或者 监控某个属性

// 直接监控对象 不建议 性能差

// watch 就是effect, 状态会收集watch effect, 属性变化后 会触发scheduler
watch(
  () => state.name,
  (newValue, oldValue) => {
    // 对象是引用地址 无法区分新的老的
    console.log(newValue, oldValue);
  }
  /* { immediate: true } */
);
state.name = "zf";

state.name = "jw";

watch.ts

function traverse(value, seen = new Set()) {
  if (!isObject(value)) {
    return value;
  }
  if (seen.has(value)) {
    return value;
  }
  seen.add(value);
  for (const k in value) {
    // 递归访问属性用于依赖收集
    traverse(value[k], seen);
  }
  return value;
}
export function isReactive(value) {
  return !!(value && value[ReactiveFlags.IS_REACTIVE]);
}
export function watch(source, cb) {
  let getter;
  if (isReactive(source)) {
    // 如果是响应式对象
    getter = () => traverse(source); // 包装成effect对应的fn, 函数内部进行遍历达到依赖收集的目的
  } else if (isFunction(source)) {
    getter = source; // 如果是函数则让函数作为fn即可
  }
  let oldValue;
  const job = () => {
    const newValue = effect.run(); // 值变化时再次运行effect函数,获取新值
    cb(newValue, oldValue);
    oldValue = newValue;
  };
  const effect = new ReactiveEffect(getter, job); // 创建effect
  oldValue = effect.run(); // 运行保存老值
}

2.监测函数

watch.ts

export function watch(source, cb) {
  let getter;
  if (isReactive(source)) {
    // 如果是响应式对象
    getter = () => traverse(source);
  } else if (isFunction(source)) {
    getter = source; // 如果是函数则让函数作为fn即可
  }
  // ...
}

3.immediate 实现

export function watch(source, cb, { immediate } = {} as any) {
  // ...
  const effect = new ReactiveEffect(getter, job); // 创建effect
  if (immediate) {
    // 需要立即执行,则立刻执行任务
    job();
  }
  oldValue = effect.run();
}

4.watch 中 cleanup 实现

连续触发 watch 时需要清理之前的 watch 操作

5.cleanUp.html script

const state = reactive({ flag: true, name: "jw", age: 30 });
let timer = 3000;
function getData(newVal) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(newVal);
    }, (timer -= 1000));
  });
}
/*
watch(
  () => state.name,
  async function (newValue) {
    let flag = true;
    while (arr.length > 0) {
      let cb = arr.shift(); // 取出来数组中的回调就销毁了
      cb();
    }
    arr.push(() => {
      // 这个函数是一个闭包
      flag = false;
      //         // 清除token
    });
    console.log("flag", flag);
    let r = await getData(newValue);
    if (flag) {
      app.innerHTML = r;
    }
  }
);
// // 什么叫闭包 定义的作用域与执行的作用域不同;
state.name = 1;
state.name = 2;*/

watch(
  state,
  async (newValue, oldValue, onCleanup) => {
    let clear = false;
    onCleanup(() => {
      clear = true;
    });
    let r = await getData(newValue.name);
    if (!clear) {
      document.body.innerHTML = r;
    }
    // 监测一个响应式值的变化
  },
  { immediate: true }
);
state.age = 31;
state.age = 32;

effect.ts

// doWatch()
// ...
let cleanup;
let onCleanup = (fn) => {
  cleanup = fn;
};
const job = () => {
  const newValue = effect.run();
  if (cleanup) cleanup(); // 下次watch执行前调用上次注册的回调
  cb(newValue, oldValue, onCleanup); // 传入onCleanup函数
  oldValue = newValue;
};
// ...

5.watchEffect

我们可以使用响应性属性编写一个方法,每当它们的任何值更新时,我们的方法就会重新运行。watchEffect 在初始化时也会立即运行

4.watchEffect.html script

const state = reactive({ flag: true, name: "jw", age: 30 });
watchEffect(() => (app.innerHTML = state.name));
setTimeout(() => {
  state.name = "Mr Lix";
}, 1000);
export function watch(source, cb, options) {
  return doWatch(source, cb, options);
}
export function watchEffect(effect, options) {
  return doWatch(effect, null, options);
}
function doWatch(source, cb, { immediate } = {} as any) {
  // ...
  const job = () => {
    if (cb) {
      const newValue = effect.run(); // 值变化时再次运行effect函数,获取新值
      if (cleanup) cleanup(); // 下次watch执行前调用上次注册的回调
      cb(newValue, oldValue, onCleanup);
      oldValue = newValue;
    } else {
      effect.run(); // 重新执行effect即可
    }
  };
  // ...
}