Skip to content

Watch & WatchEffect

1.watch 基本使用

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

watch(state, (oldValue, newValue) => {
  // 监测一个响应式值的变化
  console.log(oldValue, newValue);
});

2.监测响应式对象

reactive.ts

export function isReactive(value) {
  return !!(value && value[ReactiveFlags.IS_REACTIVE]);
}

apiWatch.ts

export function watch(source, cb, options = {}) {
  return doWatch(source, cb, options as any);
}
function doWatch(source, cb, { deep }) {
  let getter;
  // 将对象转化成getter函数
  const reactiveGetter = (source) =>
    traverse(source, deep === false ? 1 : undefined);
  // 如果是响应式对象
  if (isReactive(source)) {
    getter = () => reactiveGetter(source); // 根据深度创建getter
  }
}
// 遍历属性,会触发proxy中的get方法
function traverse(value, depth, currentDepth = 0, seen = new Set()) {
  if (!isObject(value)) {
    return value;
  }
  if (depth) {
    // 记录遍历的深度
    if (currentDepth >= depth) {
      return value;
    }
    currentDepth++;
  }
  if (seen.has(value)) {
    return value;
  }
  seen.add(value);
  for (const k in value) {
    // 递归访问属性用于依赖收集
    traverse(value[k], depth, currentDepth, seen);
  }
  return value;
}

3.创建 effect

let oldValue;
const job = () => {
  if (cb) {
    const newValue = effect.run();
    cb(newValue, oldValue);
    oldValue = newValue;
  }
};
// 创建watch对应的effect
const effect = new ReactiveEffect(getter, () => {}, job);
oldValue = effect.run(); // 运行保存老值

4.监测函数

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

5.watch 中回调执行时机

function doWatch(source, cb, { deep, immediate }) {
  const effect = new ReactiveEffect(getter, () => {}, job);

  if (cb) {
    if (immediate) {
      job();
    } else {
      oldValue = effect.run(); // 运行保存老值
    }
  }
}

6.watch 中 cleanup 实现

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

const state = reactive({ flag: true, name: "jw", age: 30 });
let i = 2000;
function getData(timer) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(timer);
    }, timer);
  });
}
watch(
  () => state.age,
  async (newValue, oldValue, onCleanup) => {
    let clear = false;
    onCleanup(() => {
      clear = true;
    });
    i -= 1000;
    let r = await getData(i); // 第一次执行1s后渲染1000, 第二次执行0s后渲染0, 最终应该是0
    if (!clear) {
      document.body.innerHTML = r;
    }
  },
  { flush: "sync" }
);
state.age = 31;
state.age = 32;
let cleanup;
let onCleanup = (fn) => {
  // 保存用户传入的清理函数
  cleanup = () => {
    fn();
    cleanup = undefined;
  };
};
const job = () => {
  if (cb) {
    const newValue = effect.run();

    if (cleanup) {
      cleanup();
    }
    // 下次回调执行前,调用清理函数
    cb(newValue, oldValue, onCleanup);
    oldValue = newValue;
  }
};

7.停止 watch

const unwatch = () => {
  effect.stop();
};
return unwatch;
stop() {
  if (this.active) { // 清理掉所有依赖
    preCleanupEffect(this);
    postCleanupEffect(this);
    this.active = false;
  }
}

8.watchEffect

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

const state = reactive({ flag: true, name: "jw", age: 30 });
watchEffect(() => (app.innerHTML = state.name));
setTimeout(() => {
  state.name = "Mr Jiang";
}, 1000);
export function watch(source, cb, options) {
  return doWatch(source, cb, options);
}
export function watchEffect(effect, options) {
  return doWatch(effect, null, options);
}
const job = () => {
  if (cb) {
    // ...
  } else {
    effect.run(); // watchEffect重新执行
  }
};
const effect = new ReactiveEffect(getter, () => {}, job);
if (cb) {
  // ...
} else {
  effect.run(); // watchEffect
}

Released under the MIT License.