前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >vue3中的watch原理你了解多少

vue3中的watch原理你了解多少

作者头像
zhouzhouya
发布2023-10-26 17:30:51
2620
发布2023-10-26 17:30:51
举报
文章被收录于专栏:一名前端开发一名前端开发

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

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也可以立即执行

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2023-10-26,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • watch api用法
  • watch Api的具体实现:
  • doWatch函数
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档