Vue源码解析系列——响应式原理篇:派发更新

准备

vue版本号2.6.12,为方便分析,选择了runtime+compiler版本。

回顾

如果有感兴趣的同学可以看看我之前的源码分析文章,这里呈上链接:《Vue源码分析系列:目录》

派发更新

前面一篇我们了解到Vue在dataget过程中收集了对应数据的Watcher实例,由这些watcher牵引着相关的依赖。这次我们要讲的是dataset过程中的逻辑,在get中收集了watcher之后,在set过程中就会通知这些watcher,由这些watcher去更新依赖,这就是今天要讲的派发更新的过程。

defineReactive

进入defineReactive查看set过程的操作:

//设置该值时,派发更新
    set: function reactiveSetter(newVal) {
      const value = getter ? getter.call(obj) : val;
      /* eslint-disable no-self-compare */
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return;
      }
      /* eslint-enable no-self-compare */
      if (process.env.NODE_ENV !== "production" && customSetter) {
        customSetter();
      }
      // #7981: for accessor properties without setter
      if (getter && !setter) return;
      if (setter) {
        setter.call(obj, newVal);
      } else {
        val = newVal;
      }
      childOb = !shallow && observe(newVal);
      dep.notify();
    },

前面的一大串都可以忽略不看,直接看最后一行dep.notify()
进入dep.notify()

dep.notify()

notify () {
    // stabilize the subscriber list first
    const subs = this.subs.slice()
    if (process.env.NODE_ENV !== 'production' && !config.async) {
      // subs aren't sorted in scheduler if not running async
      // we need to sort them now to make sure they fire in correct
      // order
      subs.sort((a, b) => a.id - b.id)
    }
    for (let i = 0, l = subs.length; i < l; i++) {
      subs[i].update()
    }

一开始对实例上的subs列表进行了一次浅拷贝。然后遍历这个Watcher列表,调用Watcher实例上的update方法。

watcher.update()

  update() {
    /* istanbul ignore else */
    if (this.lazy) {
      this.dirty = true;
    } else if (this.sync) {
      this.run();
    } else {
      queueWatcher(this);
    }
  }

判断是不是一个懒watcher(也就是之后篇章会介绍到的computed watcher),显然这里不是,再判断是不是一个同步wather,显然我们没有定义过什么同步属性,所以直接进入else,也就是调用queueWatcher方法。

queueWatcher

export function queueWatcher (watcher: Watcher) {
  const id = watcher.id
  if (has[id] == null) {
    has[id] = true
    if (!flushing) {
      queue.push(watcher)
    } else {
      // if already flushing, splice the watcher based on its id
      // if already past its id, it will be run next immediately.
      let i = queue.length - 1
      while (i > index && queue[i].id > watcher.id) {
        i--
      }
      queue.splice(i + 1, 0, watcher)
    }
    // queue the flush
    if (!waiting) {
      waiting = true

      if (process.env.NODE_ENV !== 'production' && !config.async) {
        flushSchedulerQueue()
        return
      }
      nextTick(flushSchedulerQueue)
    }
  }
}

先判断是不是正在flushing(默认为false),flushing这个变量我们接下来就能见到,这里为false,所以执行queue.push(watcher);再往下,判断是不是正在waiting(默认为false),这里为false,进入判断,然后更改waitingtrue,之后直接nextTick(flushSchedulerQueue),这个nextTick我们接下来几篇会讲到,我们这边直接就当做调用了flushSchedulerQueue,进入flushSchedulerQueue

flushSchedulerQueue

function flushSchedulerQueue () {
  currentFlushTimestamp = getNow()
  flushing = true
  let watcher, id

  // Sort queue before flush.
  // This ensures that:
  // 1. Components are updated from parent to child. (because parent is always
  //    created before the child)
  // 2. A component's user watchers are run before its render watcher (because
  //    user watchers are created before the render watcher)
  // 3. If a component is destroyed during a parent component's watcher run,
  //    its watchers can be skipped.
  queue.sort((a, b) => a.id - b.id)

  // do not cache length because more watchers might be pushed
  // as we run existing watchers
  for (index = 0; index < queue.length; index++) {
    watcher = queue[index]
    if (watcher.before) {
      watcher.before()
    }
    id = watcher.id
    has[id] = null
    watcher.run()
    // in dev build, check and stop circular updates.
    if (process.env.NODE_ENV !== 'production' && has[id] != null) {
      circular[id] = (circular[id] || 0) + 1
      if (circular[id] > MAX_UPDATE_COUNT) {
        warn(
          'You may have an infinite update loop ' + (
            watcher.user
              ? `in watcher with expression "${watcher.expression}"`
              : `in a component render function.`
          ),
          watcher.vm
        )
        break
      }
    }
  }

  // keep copies of post queues before resetting state
  const activatedQueue = activatedChildren.slice()
  const updatedQueue = queue.slice()

  resetSchedulerState()

  // call component updated and activated hooks
  callActivatedHooks(activatedQueue)
  callUpdatedHooks(updatedQueue)

  // devtool hook
  /* istanbul ignore if */
  if (devtools && config.devtools) {
    devtools.emit('flush')
  }
}

一上来先是更改了flushingtrue。之后对queue待更新队列进行了一个排序。在Vue的源码注释中可以看到为什么要排序:

  • 数据更新是从父传到子的
  • 用户自定义的watcher需要在渲染watcher之前执行
  • 如果在父组件watcher执行是销毁实例,就不需要执行子watcher

然后就是遍历这个watcher队列,如果实例上存在before方法,就执行before方法,在mountComponent中可以看到一个渲染watcher实例化时回传入一个before方法:

new Watcher(
   vm,
   updateComponent,
   noop,
   {
     before() {
       if (vm._isMounted && !vm._isDestroyed) {
         callHook(vm, "beforeUpdate");
       }
     },
   },
   true /* isRenderWatcher */
 );

所以这个时候会触发beforeUpdate钩子函数,接下来调用了watcher实例上的run方法,这个我们一会看,继续往下,之后是一个无限循环判断,之后调用resetSchedulerState,在这里面将watingflushing至为false。 最后是keep-alive的两个钩子。
接下来我们进入watcher.run()

watcher.run()

  run() {
    if (this.active) {
      const value = this.get();
      if (
        value !== this.value ||
        // Deep watchers and watchers on Object/Arrays should fire even
        // when the value is the same, because the value may
        // have mutated.
        isObject(value) ||
        this.deep
      ) {
        // set new value
        const oldValue = this.value;
        this.value = value;
        if (this.user) {
          try {
            this.cb.call(this.vm, value, oldValue);
          } catch (e) {
            handleError(
              e,
              this.vm,
              `callback for watcher "${this.expression}"`
            );
          }
        } else {
          this.cb.call(this.vm, value, oldValue);
        }
      }
    }
  }

先调用了get

  • 如果这里的watcher是个渲染Watcher,也就是在mountComponent中实例化的Watcher,调用这个get方法后就会去执行updateComponent方法,也就是renderpatch的过程。
  • 如果是用户定义的普通Watcher,调用这个get就会获取数据的最新值,然后比较新旧值,如果是数据类型是对象的话还有deep选项来进行深度比较。如果新旧值不一致就会调用用户定义的回调函数并传入新旧值。

到这,派发更新的过程就解析完毕了。

总结

经过对以上源码的阅读我们了解到,现在我们认识的Watcher有两种类型,一种是渲染Watcher,一种是用户自定义的Watcher,如果是渲染Watcher,在派发更新的过程中会执行patch方法,也就是更新DOM;如果是用户定义的Watcher,就会调用用户定义的回调函数,并传入数据的新旧值。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爱学习的前端小黄

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值