vue3中的watch原理你了解多少

2023-10-26 17:30:51 浏览数 (2)

本篇文章为学习笔记,都是一个字一个字敲出来的,如果有和视频重叠部分,侵权行为告知立马删除。

watch api用法

1.侦听getter函数

代码语言:javascript复制
const text = reactive({
    a: 1
})
watch(() => text.a,(newValue,oldValue)=>{
    //当text.a发生变化时,会触发此回调函数
    console.log('oldValue: ', oldValue);
    console.log('newValue: ', newValue);
});

2.侦听响应式对象

代码语言:javascript复制
const text = ref(0)    
watch(text,(newValue,oldValue)=> {
    //当text.value发生变化时,会触发此回调函数
    console.log('oldValue: ', oldValue); 
    console.log('newValue: ', newValue);
})

3.什么是侦听器 当侦听的对象或函数发生了变化则自动执行某个回调函数,与副作用effect函数很像,那它的内部是否依赖effect?

watch Api的具体实现:

代码语言:javascript复制
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(fn, options?)` signature has been moved to a separate API. `  
        `Use `watchEffect(fn, options?)` instead. `watch` now only `  
        `supports `watch(source, cb, options?) signature.`
    )
  }
  return doWatch(source as any, cb, options)
}

从源码中看到,watch Api内部调用了doWatch函数,判断cb是否是一个函数,如果不是,则使用effect APi

1.标准化source的流程

代码语言:javascript复制
 const warnInvalidSource = (s: unknown) => {
    warn(
      `Invalid watch source: `,
      s,
      `A watch source can only be a getter/effect function, a ref, `  
        `a reactive object, or an array of these types.`
    )
  }
代码语言:javascript复制
function traverse(value: unknown, seen?: Set<unknown>) {
  if (!isObject(value) || (value as any)[ReactiveFlags.SKIP]) {
    return value
  }
  seen = seen || new Set()
  if (seen.has(value)) {
    return value
  }
  seen.add(value)
  if (isRef(value)) {
    traverse(value.value, seen)
  } else if (isArray(value)) {
    for (let i = 0; i < value.length; i  ) {
      traverse(value[i], seen)
    }
  } else if (isSet(value) || isMap(value)) {
    value.forEach((v: any) => {
      traverse(v, seen)
    })
  } else if (isPlainObject(value)) {
    for (const key in value) {
      traverse((value as any)[key], seen)
    }
  }
  return value
}

traverse函数使用递归的方式访问value的子属性,因为deep属于watcher的一个配置选项。 2.cb参数 在回调函数中,会提供最新的 value、旧 value,以及 onCleanup 函数用以清除副作用。

代码语言:javascript复制
export type WatchCallback<V = any, OV = any> = (
  value: V,
  oldValue: OV,
  onCleanup: OnCleanup
) => any

3.options的创建

代码语言:javascript复制
export interface WatchOptionsBase extends DebuggerOptions {
  flush?: 'pre' | 'post' | 'sync'
}

export interface WatchOptions<Immediate = boolean> extends WatchOptionsBase {
  immediate?: Immediate
  deep?: boolean
}

options 的类型 WatchOptions 继承了 WatchOptionsBase,这也就是 watch 除了 immediate 和 deep 这两个特有的参数外,还可以传递 WatchOptionsBase 中的所有参数以控制副作用执行的行为。 Options 中的 flush 决定了 watcher 的执行时机:

代码语言:javascript复制
 if (flush === 'sync') {
    scheduler = job as any // the scheduler function gets called directly
  } else if (flush === 'post') {
    scheduler = () => queuePostRenderEffect(job, instance && instance.suspense)
  } else {
    // default: 'pre'
    job.pre = true
    if (instance) job.id = instance.uid
    scheduler = () => queueJob(job)
  }

当flush为 sync 的时,表示它是一个同步 watcher,即当数据变化时同步执行回调函数。 当flush为 pre的时,回调函数通过 queueJob 的方式在组件更新之前执行。如果组件还没挂载,则同步执行确保回调函数在组件挂载之前执行。 如果没设置flush,回调函数通过 queuePostRenderEffect 的方式在组件更新之后执行

doWatch函数

代码语言:javascript复制
if (isRef(source)) {
    getter = () => source.value
    forceTrigger = isShallow(source)
  } else if (isReactive(source)) {
    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)
  }

source标准化根据source的类型划分: 如果source是ref对象,则创建一个访问source.value的getter函数。 如果source是reactive对象,则创建一个访问source的getter函数,并设置deep为true。 如果source是函数,则进一步判断cb是否存在,对于watch Api来说,cb一定存在,且是一个回调函数,getter就是一个简单的对source函数封装的函数。 如果source不满足上述条件,则在非生产环境下报警告,提示source类型不合法。

当deep为true时,会用traverse函数把getter再包装一层。

代码语言:javascript复制
if (cb && deep) {
    const baseGetter = getter
    getter = () => traverse(baseGetter())
}

定义清除副作用函数

代码语言:javascript复制
// 清除副作用函数
let cleanup: () => void
let onCleanup: OnCleanup = (fn: () => void) => {
  cleanup = effect.onStop = () => {
    callWithErrorHandling(fn, instance, ErrorCodes.WATCH_CLEANUP)
  }
}

总结

  • watch 作用是对传入的某个或多个值的变化进行监听;触发时会返回新值和老值;也就是说第一次不会执行,只有变化时才会重新执行
  • watchEffect 是传入一个立即执行函数,所以默认第一次也会执行一次;不需要传入监听内容,会自动收集函数内的数据源作为依赖,在依赖变化的时候又会重新执行该函数,如果没有依赖就不会执行;而且不会返回变化前后的新值和老值

watchImmediate也可以立即执行

0 人点赞