vue3-watch的简易实现(4)

71 阅读2分钟

今天来看一下 watch 的实现方式

watch 侦听一个或多个响应式数据源,并在数据源变化时调用所给的回调函数。本质上跟 computed 一样,都是利用了effect去做一些事情。

基本实现

在 package/reactivity/src 新建 watch.ts 文件

export function watch(source, callback, options) {
    let getter;
    // 根据值的类型, 封装成getter函数
    if (isReactive(source)) {
        getter = () => source;
    } else if (isFunction(source)) {
        getter = source;
    } else if (isRef(source)) {
        getter = () => source.value
    } else {
        // ...其他情况的
    }
    
    let oldValue
    const job = () => {
        callback()
    }
    // 创建effect的时候, 会调用getter函数收集依赖, 到时候变动触发job函数
    let effect = new ReactiveEffect(getter, job);
    
    oldValue = effect.run() // 第一次运行返回的值保存起来
    
    return effect.stop.bind(effect) // 可以通过调用stop函数停止watch监听
}

isReactive 和 isRef 在 reactive 文件中导出

export function isReactive(target) {
  return target && !!target[reactiveFlags.IS_REACTIVE];
}

export function isRef(target) {
  return target && !!target[reactiveFlags.IS_REF];
}

baseHandles 加入新枚举

export const enum reactiveFlags {
  IS_REACTIVE = "__v_isReactive",
  IS_REF = "__v_isRef",
}

traverse

如果传入的是一个proxy对象, 那么需要遍历该值的所有属性, 触发get的收集函数, 才能在变动的时候触发更新, 这里也可以看出来如果传入的是proxy对象, 默认是要做递归处理的, 所以尽量监听单个属性比较好

function traverse(value, seen = new Set()) {
  if (!isObject(value)) {
    return value;
  }
  // 为了防止重复引用导致死循环
  if (seen.has(value)) {
    return value;
  }

  seen.add(value);
  // 递归访问所有属性进行一个依赖搜集
  for (const key in value) {
    traverse(value[key], seen);
  }

  return value;
}

// 这一步也要修改一下
if (isReactive(source)) {
   getter = () => traverse(source);
}

新旧值的传递

const job = () => {
    let newValue = effect.run(); // 重新运行一次得到新值
    callback(newValue, oldValue); 
    oldValue = newValue; // 旧值改为新值
};

onCleanup

这个副作用函数的参数也是一个函数,用来注册清理回调。清理回调会在该副作用下一次执行前被调用,可以用来清理无效的副作用。

 let cleanup;
 function onCleanup(fn) {
    cleanup = fn; // 相当于第一次调用只是赋值
 }
 let oldValue;
 const job = () => {
    let newValue = effect.run();
    if (cleanup) cleanup(); // 第一次还没有赋值完成, 所以无法调用, 等到第二次进入有值了才进行调用
    callback(newValue, oldValue, onCleanup);
    oldValue = newValue;
 };