返回顶部
首页 > 资讯 > 精选 >Vue3 computed和watch源码分析
  • 403
分享到

Vue3 computed和watch源码分析

2023-07-05 17:07:06 403人浏览 安东尼
摘要

这篇文章主要介绍“vue3 computed和watch源码分析”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Vue3 computed和watch源码分析”文章能帮助大家解决问题。computed

这篇文章主要介绍“vue3 computed和watch源码分析”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Vue3 computed和watch源码分析”文章能帮助大家解决问题。

    computed

    computed和watch在面试中经常被问到他们的区别,那么我们就从源码的实现来看看他们的具体实现

    // packages/Reactivity/src/computed.tsexport function computed<T>(  getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>,  debuGoptions?: DebuggerOptions,  isSSR = false) {  let getter: ComputedGetter<T>  let setter: ComputedSetter<T>  const onlyGetter = isFunction(getterOrOptions)  if (onlyGetter) {    getter = getterOrOptions    setter = __DEV__      ? () => {          console.warn('Write operation failed: computed value is readonly')        }      : NOOP  } else {    getter = getterOrOptions.get    setter = getterOrOptions.set  }  // new ComputedRefImpl  const cRef = new ComputedRefImpl(getter, setter, onlyGetter || !setter, isSSR)  if (__DEV__ && debugOptions && !isSSR) {    cRef.effect.onTrack = debugOptions.onTrack    cRef.effect.onTrigger = debugOptions.onTrigger  }  // 返回ComputedRefImpl实例  return cRef as any}

    可以看到computed内部只是先处理getter和setter,然后new一个ComputedRefImpl返回,如果你知道ref api的实现,可以发现他们的实现有很多相同之处

    ComputedRefImpl

    // packages/reactivity/src/computed.tsexport class ComputedRefImpl<T> {  public dep?: Dep = undefined // 存储effect的集合  private _value!: T  public readonly effect: ReactiveEffect<T>  public readonly __v_isRef = true  public readonly [ReactiveFlags.IS_READONLY]: boolean = false  public _dirty = true // 是否需要重新更新value  public _cacheable: boolean  constructor(    getter: ComputedGetter<T>,    private readonly _setter: ComputedSetter<T>,    isReadonly: boolean,    isSSR: boolean  ) {    // 创建effect    this.effect = new ReactiveEffect(getter, () => {      // 调度器执行 重新赋值_dirty为true      if (!this._dirty) {        this._dirty = true        // 触发effect        triggerRefValue(this)      }    })    // 用于区分effect是否是computed    this.effect.computed = this    this.effect.active = this._cacheable = !isSSR    this[ReactiveFlags.IS_READONLY] = isReadonly  }  get value() {    // the computed ref may get wrapped by other proxies e.g. readonly() #3376    // computed ref可能被其他代理包装,例如readonly() #3376    // 通过toRaw()获取原始值    const self = toRaw(this)    // 收集effect    trackRefValue(self)    // 如果是脏的,重新执行effect.run(),并且将_dirty设置为false    if (self._dirty || !self._cacheable) {      self._dirty = false      // run()方法会执行getter方法 值会被缓存到self._value      self._value = self.effect.run()!    }    return self._value  }  set value(newValue: T) {    this._setter(newValue)  }}

    可以看到ComputedRefImplget的get实现基本和ref的get相同(不熟悉ref实现的请看上一章),唯一的区别就是_dirty值的判断,这也是我们常说的computed会缓存value,那么computed是如何知道value需要更新呢?

    可以看到在computed构造函数中,会建立一个getter与其内部响应式数据的关系,这跟我们组件更新函数跟响应式数据建立关系是一样的,所以与getter相关的响应式数据发生修改的时候,就会触发getter effect 对应的scheduler,这里会将_dirty设置为true并去执行收集到的effect(这里通常是执行get里收集到的函数更新的effect),然后就会去执行函数更新函数,里面会再次触发computed的get,此时dirty已经被置为true,就会重新执行getter获取新的值返回,并将该值缓存到_vlaue。

    小结:

    所以computed是有两层的响应式处理的,一层是computed.value和函数的effect之间的关系(与ref的实现相似),一层是computed的getter和响应式数据的关系。

    注意:如果你足够细心就会发现函数更新函数的effect触发和computed getter的effect的触发之间可能存在顺序的问题。假如有一个响应式数据a不仅存在于getter中,还在函数render中早于getter被访问,此时a对应的dep中更新函数的effect就会早于getter的effect被收集,如果此时a被改变,就会先执行更新函数的effect,那么此时render函数访问到computed.value的时候就会发现_dirty依然是false,因为getter的effect还没有被执行,那么此时依然会是旧值。vue3中对此的处理是执行effects的时候会优先执行computed对应的effect(此前章节也有提到):

    // packages/reactivity/src/effect.tsexport function triggerEffects(  dep: Dep | ReactiveEffect[],  debuggerEventExtraInfo?: DebuggerEventExtraInfo) {  // spread into array for stabilization  const effects = isArray(dep) ? dep : [...dep]  // computed的effect会先执行  // 防止render获取computed值得时候_dirty还没有置为true  for (const effect of effects) {    if (effect.computed) {      triggerEffect(effect, debuggerEventExtraInfo)    }  }  for (const effect of effects) {    if (!effect.computed) {      triggerEffect(effect, debuggerEventExtraInfo)    }  }}

    watch

    watch相对于computed要更简单一些,因为他只用建立getter与响应式数据之间的关系,在响应式数据变化时调用用户传过来的回调并将新旧值传入即可

    // packages/runtime-core/src/apiWatch.tsexport function watch<T = any, Immediate extends Readonly<boolean> = false>(  source: T | WatchSource<T>,  cb: any,  options?: WatchOptions<Immediate>): WatchStopHandle {  if (__DEV__ && !isFunction(cb)) {    warn(...)  }  // watch 具体实现  return doWatch(source as any, cb, options)}
    function doWatch(  source: WatchSource | WatchSource[] | WatchEffect | object,  cb: WatchCallback | null,  { immediate, deep, flush, onTrack, onTrigger }: WatchOptions = EMPTY_OBJ): WatchStopHandle {  if (__DEV__ && !cb) {    ...  }  const warnInvalidSource = (s: unknown) => {    warn(...)  }  const instance =    getCurrentScope() === currentInstance?.scope ? currentInstance : null  // const instance = currentInstance  let getter: () => any  let forceTrigger = false  let isMultiSource = false  // 根据不同source 创建不同的getter函数  // getter 函数与computed的getter函数作用类似  if (isRef(source)) {    getter = () => source.value    forceTrigger = isshallow(source)  } else if (isReactive(source)) {    // source是reactive对象时 自动开启deep=true    getter = () => source    deep = true  } else if (isArray(source)) {    isMultiSource = true    forceTrigger = source.some(s => isReactive(s) || isShallow(s))    getter = () =>      source.map(s => {        if (isRef(s)) {          return s.value        } else if (isReactive(s)) {          return traverse(s)        } else if (isFunction(s)) {          return callWithErrorHandling(s, instance, ErrorCodes.WATCH_GETTER)        } else {          __DEV__ && warnInvalidSource(s)        }      })  } else if (isFunction(source)) {    if (cb) {      // getter with cb      getter = () =>        callWithErrorHandling(source, instance, ErrorCodes.WATCH_GETTER)    } else {      // no cb -> simple effect      getter = () => {        if (instance && instance.isUnmounted) {          return        }        if (cleanup) {          cleanup()        }        return callWithAsyncErrorHandling(          source,          instance,          ErrorCodes.WATCH_CALLBACK,          [onCleanup]        )      }    }  } else {    getter = NOOP    __DEV__ && warnInvalidSource(source)  }  // 2.x array mutation watch compat  // 兼容vue2  if (__COMPAT__ && cb && !deep) {    const baseGetter = getter    getter = () => {      const val = baseGetter()      if (        isArray(val) &&        checkCompatEnabled(DeprecationTypes.WATCH_ARRAY, instance)      ) {        traverse(val)      }      return val    }  }  // 深度监听  if (cb && deep) {    const baseGetter = getter    // traverse会递归遍历对象的所有属性 以达到深度监听的目的    getter = () => traverse(baseGetter())  }  let cleanup: () => void  // watch回调的第三个参数 可以用此注册一个cleanup函数 会在下一次watch cb调用前执行  // 常用于竞态问题的处理  let onCleanup: OnCleanup = (fn: () => void) => {    cleanup = effect.onStop = () => {      callWithErrorHandling(fn, instance, ErrorCodes.WATCH_CLEANUP)    }  }  // in SSR there is no need to setup an actual effect, and it should be noop  // unless it's eager or sync flush  let ssrCleanup: (() => void)[] | undefined  if (__SSR__ && isInSSRComponentSetup) {    // ssr处理 ...  }  // oldValue 声明 多个source监听则初始化为数组  let oldValue: any = isMultiSource    ? new Array((source as []).length).fill(INITIAL_WATCHER_VALUE)    : INITIAL_WATCHER_VALUE  // 调度器调用时执行  const job: SchedulerJob = () => {    if (!effect.active) {      return    }    if (cb) {      // watch(source, cb)      // 获取newValue      const newValue = effect.run()      if (        deep ||        forceTrigger ||        (isMultiSource          ? (newValue as any[]).some((v, i) =>              hasChanged(v, (oldValue as any[])[i])            )          : hasChanged(newValue, oldValue)) ||        (__COMPAT__ &&          isArray(newValue) &&          isCompatEnabled(DeprecationTypes.WATCH_ARRAY, instance))      ) {        // cleanup before running cb again        if (cleanup) {          // 执行onCleanup传过来的函数          cleanup()        }        // 调用cb 参数为newValue、oldValue、onCleanup        callWithAsyncErrorHandling(cb, instance, ErrorCodes.WATCH_CALLBACK, [          newValue,          // pass undefined as the old value when it's changed for the first time          oldValue === INITIAL_WATCHER_VALUE            ? undefined            : isMultiSource && oldValue[0] === INITIAL_WATCHER_VALUE            ? []            : oldValue,          onCleanup        ])        // 更新oldValue        oldValue = newValue      }    } else {      // watchEffect      effect.run()    }  }  // important: mark the job as a watcher callback so that scheduler knows  // it is allowed to self-trigger (#1727)  job.allowRecurse = !!cb  let scheduler: EffectScheduler  if (flush === 'sync') {    // 同步更新 即每次响应式数据改变都会回调一次cb 通常不使用    scheduler = job as any // the scheduler function gets called directly  } else if (flush === 'post') {    // job放入pendingPostFlushCbs队列中    // pendingPostFlushCbs队列会在queue队列执行完毕后执行 函数更新effect通常会放在queue队列中    // 所以pendingPostFlushCbs队列执行时组件已经更新完毕    scheduler = () => queuePostRenderEffect(job, instance && instance.suspense)  } else {    // default: 'pre'    job.pre = true    if (instance) job.id = instance.uid    // 默认异步更新 关于异步更新会和nextTick放在一起详细讲解    scheduler = () => queueJob(job)  }  // 创建effect effect.run的时候建立effect与getter内响应式数据的关系  const effect = new ReactiveEffect(getter, scheduler)  if (__DEV__) {    effect.onTrack = onTrack    effect.onTrigger = onTrigger  }  // initial run  if (cb) {    if (immediate) {      // 立马执行一次job      job()    } else {      // 否则执行effect.run() 会执行getter 获取oldValue      oldValue = effect.run()    }  } else if (flush === 'post') {    queuePostRenderEffect(      effect.run.bind(effect),      instance && instance.suspense    )  } else {    effect.run()  }  // 返回一个取消监听的函数  const unwatch = () => {    effect.stop()    if (instance && instance.scope) {      remove(instance.scope.effects!, effect)    }  }  if (__SSR__ && ssrCleanup) ssrCleanup.push(unwatch)  return unwatch}

    关于“Vue3 computed和watch源码分析”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识,可以关注编程网精选频道,小编每天都会为大家更新不同的知识点。

    --结束END--

    本文标题: Vue3 computed和watch源码分析

    本文链接: https://lsjlt.com/news/353934.html(转载时请注明来源链接)

    有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

    猜你喜欢
    软考高级职称资格查询
    编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
    • 官方手机版

    • 微信公众号

    • 商务合作