手摸手从0实现简版Vue ---(watch实现)

726 阅读1分钟

接:

手摸手从0实现简版Vue --- (对象劫持)

手摸手从0实现简版Vue --- (数组劫持)

手摸手从0实现简版Vue --- (模板编译)

手摸手从0实现简版Vue --- (依赖收集)

手摸手从0实现简版Vue --- (批量更新&nextTick)

watch的两种用法

使用watch有两种方法,第一种直接调用vm.$watch,第二种是在选项中配置watch属性。

watch: {
  msg: {
    handler: function (newValue, oldValue) {
      console.log('watch:', {
        newValue,
        oldValue
      })
    },
    immediate: true
  }
}

// or

vm.$watch('msg', function(newVal, oldVal) {
  console.log({ newVal, oldVal })
})

我们要去实现一个vm.$watch方法,$watch方法的话做了两件事:

  1. userDef 中分离 handler 和 其他的 opts
  2. new一个Watcher,并且增加 { user: true } 标记为用户watcher。

下面看代码:

Vue.prototype.$watch = function(expr, userDef) {
  const vm = this;
  let handler = userDef;
  const opts = { user: true }
  if (userDef.handler) {
    handler = userDef.handler;
    Object.assign(opts, userDef);
  }
  new Watcher(vm, expr, handler, opts);
}

Watcher 内部实现

首先把传入的字符串做为函数返回,例如'msg'转化为 util.getValue(vm, 'msg')

这一步非常关键,因为new Watcher的时候默认调用一次get方法,然后执行getter函数,这个过程会触发msggetter,让msgdep添加一个用户watcher,完成依赖收集。

  constructor(vm, exprOrFn, cb = () => {}, opts = {}) {
    this.vm = vm;
    this.exprOrFn = exprOrFn;
    if (typeof exprOrFn === 'function') {
      this.getter = exprOrFn;
    } else if (typeof exprOrFn === 'string') {
+      // 用户watcher
+      // 解析expr,取到data上的值
+      // 取值的时候完成依赖收集
+      this.getter = function () {
+        return util.getValue(vm, exprOrFn);
+      }
+    }
+   this.immediate = opts.immediate
+    // 用户添加的watcher,标记一下
+    this.user = opts.user
    this.cb = cb;
    this.opts = opts;
    this.id = id++;
    this.deps = [];
    this.depsId = new Set();
    // 我们希望在回调函数中返回一个新值,一个旧值,所以我们需要记录getter返回的值  
+    this.value = this.get();
+    if (this.immediate) {
+      this.cb(this.value)
+    }
  }

完成依赖收集后,当我们的数据发生变化后,调用Watcherrun方法,进行值的比对,如果发生变化,就去执行这个watchercallback

class Watcher {
  run() {
    const newValue = this.get();
    if (newValue !== this.value) {
      this.cb(newValue, this.value);
      this.value = newValue;
    }
  }
}

这样的话,我们的 $watch 也就实现了,下面我们去实现initWatch方法,遍历下用户传入的watch配置,进行watcher添加:

function initWatch(vm) {
  const watch = vm.$options.watch;
  for (const key in watch) {
    const userDef = watch[key];
    createWatcher(vm, key, userDef);
  }
} 

此时我们使用最开始的两种方法去监听我们的msg值的变化,然后异步去更新一下我们的msg值,两个log都会正常执行了。

这样的话我们的watch也就简单实现啦~

代码点击=> 传送门