细读Vue3 watch核心源码和原理实现(喝杯水干完!!!)

315 阅读28分钟

最近在自学Java一些基础知识,并不是打算做一个全干(栈)工程师。。。只是为了更好了解整个前端的完整链条以及整个前端前后端完整交互的生命周期~让自己成为更专业的前端,也有利于看前端一些框架源码分析(更多的是为了自己35后路铺路开路目标是坚持做一个热心分享前端知识,自由职业者拼凑能力哈哈哈)。。嘿嘿,废话不多说笨鸟先飞!!!继续上干货。个人公众号:鱼樱AI实验室

watch基础用法

watch 是 Vue 3 中用于观察和响应响应式数据变化的函数。它比 watchEffect 更灵活,因为它允许你明确指定要观察的数据源,并且可以配置更多的选项。

import { ref, watch } from 'vue';

export default {
  setup() {
    const count = ref(0);

    watch(count, (newVal, oldVal) => {
      console.log('count 变化了:', oldVal, '->', newVal);
    });

    return {
      count
    };
  }
};

下面是一个完整的 Vue 3 组件示例,使用 script setup 语法,完整demo含有这些内容观察单个数据源:通过一个响应式引用或一个 getter 函数。 观察多个数据源:通过一个数组,数组中的每个元素可以是响应式引用或 getter 函数。 立即执行:使用 immediate: true 选项。 深度监听:使用 deep: true 选项。 停止监听:调用 watch 返回的停止函数。

<template>
  <div>
    <h1>Watch 示例</h1>
    <p>Count: {{ count }}</p>
    <button @click="incrementCount">Increment Count</button>
    <p>FirstName: {{ firstName }}</p>
    <p>LastName: {{ lastName }}</p>
    <button @click="changeName">Change Name</button>
    <p>User Name: {{ user.name }}</p>
    <p>User City: {{ user.address.city }}</p>
    <button @click="changeUserCity">Change User City</button>
    <p>Some Condition: {{ someCondition }}</p>
    <button @click="toggleCondition">Toggle Condition</button>
  </div>
</template>

<script setup>
import { ref, reactive, watch } from 'vue';

// 创建响应式引用
const count = ref(0);
const firstName = ref('John');
const lastName = ref('Doe');
const someCondition = ref(false);

// 创建响应式对象
const user = reactive({
  name: 'John',
  address: {
    city: 'New York'
  }
});

// 观察单个数据源:通过一个响应式引用
watch(count, (newVal, oldVal) => {
  console.log('count 变化了:', oldVal, '->', newVal);
});

// 观察多个数据源:通过一个数组,数组中的每个元素可以是响应式引用或 getter 函数
watch([firstName, lastName], ([newFirstName, newLastName], [oldFirstName, oldLastName]) => {
  console.log('名字变化了:', oldFirstName, oldLastName, '->', newFirstName, newLastName);
});

// 观察单个数据源:通过一个 getter 函数
watch(() => user.name, (newName, oldName) => {
  console.log('user.name 变化了:', oldName, '->', newName);
});

// 深度监听:通过一个 getter 函数,并使用 deep: true 选项
watch(() => user.address.city, (newCity, oldCity) => {
  console.log('user.address.city 变化了:', oldCity, '->', newCity);
}, {
  deep: true
});

// 立即执行:使用 immediate: true 选项
watch(count, (newVal, oldVal) => {
  console.log('count 立即执行:', oldVal, '->', newVal);
}, {
  immediate: true
});

// 停止监听
const stopUserWatch = watch(user, (newUser, oldUser) => {
  console.log('user 变化了:', oldUser, '->', newUser);
});

// 在某个条件下停止监听
watch(someCondition, (newValue) => {
  if (newValue) {
    stopUserWatch();
    console.log('user 的监听已停止');
  } else {
    console.log('user 的监听已恢复');
  }
});

// 方法定义
const incrementCount = () => {
  count.value++;
};

const changeName = () => {
  firstName.value = 'Jane';
  lastName.value = 'Smith';
};

const changeUserCity = () => {
  user.address.city = 'Los Angeles';
};

const toggleCondition = () => {
  someCondition.value = !someCondition.value;
};
</script>

<style scoped>
button {
  margin: 5px;
  padding: 5px 10px;
  cursor: pointer;
}
</style>

watchEffect基础用法

watchEffect 是 Vue 3 提供的一个用于自动追踪依赖并执行副作用的函数。它会在创建时立即执行一次,并在依赖的数据发生变化时再次执行

<template>
  <div>
    <h1>WatchEffect 示例</h1>
    <p>Count: {{ count }}</p>
    <button @click="incrementCount">Increment Count</button>
    <p>FirstName: {{ firstName }}</p>
    <p>LastName: {{ lastName }}</p>
    <button @click="changeName">Change Name</button>
    <p>User Name: {{ user.name }}</p>
    <p>User City: {{ user.address.city }}</p>
    <button @click="changeUserCity">Change User City</button>
    <p>Some Condition: {{ someCondition }}</p>
    <button @click="toggleCondition">Toggle Condition</button>
  </div>
</template>

<script setup>
import { ref, reactive, watchEffect } from 'vue';

// 创建响应式引用
const count = ref(0);
const firstName = ref('John');
const lastName = ref('Doe');
const someCondition = ref(false);

// 创建响应式对象
const user = reactive({
  name: 'John',
  address: {
    city: 'New York'
  }
});

// 使用 watchEffect 自动追踪依赖并执行副作用
watchEffect(() => {
  console.log('count 变化了:', count.value);
});

watchEffect(() => {
  console.log('名字变化了:', firstName.value, lastName.value);
});

watchEffect(() => {
  console.log('user.name 变化了:', user.name);
});

watchEffect(() => {
  console.log('user.address.city 变化了:', user.address.city);
});

// 停止监听
const stopUserWatchEffect = watchEffect(() => {
  console.log('user 变化了:', user.name, user.address.city);
});

watchEffect(() => {
  if (someCondition.value) {
    stopUserWatchEffect();
    console.log('user 的 watchEffect 已停止');
  } else {
    console.log('user 的 watchEffect 已恢复');
  }
});

// 方法定义
const incrementCount = () => {
  count.value++;
};

const changeName = () => {
  firstName.value = 'Jane';
  lastName.value = 'Smith';
};

const changeUserCity = () => {
  user.address.city = 'Los Angeles';
};

const toggleCondition = () => {
  someCondition.value = !someCondition.value;
};
</script>

<style scoped>
button {
  margin: 5px;
  padding: 5px 10px;
  cursor: pointer;
}
</style>
  • 自动追踪依赖watchEffect 会自动追踪回调函数中使用的响应式数据的变化。
  • 立即执行watchEffect 在创建时会立即执行一次回调函数。
  • 停止监听watchEffect 返回一个停止函数,可以手动停止监听。

watch 和 watchEffect 源码实现

watchEffect 的源码主要位于 packages/runtime-core/src/apiWatch.ts 文件中,watch 函数的实现同样位于 packages/runtime-core/src/apiWatch.ts 文件中。

// packages\runtime-core\src\apiWatch.ts
import {
  type WatchOptions as BaseWatchOptions,  // 导入基础的 WatchOptions 类型
  type DebuggerOptions,  // 导入调试器选项类型
  type ReactiveMarker,  // 导入反应式标记类型
  type WatchCallback,  // 导入监听回调函数类型
  type WatchEffect,  // 导入监听效果函数类型
  type WatchHandle,  // 导入监听句柄类型
  type WatchSource,  // 导入监听源类型
  watch as baseWatch,  // 导入基础的 watch 函数
} from '@vue/reactivity'

import { type SchedulerJob, SchedulerJobFlags, queueJob } from './scheduler'  // 导入调度器任务类型及其标志,以及用于排队任务的 queueJob 函数

import { EMPTY_OBJ, NOOP, extend, isFunction, isString } from '@vue/shared'  // 导入一些工具函数和常量

import {
  type ComponentInternalInstance,  // 导入组件内部实例类型
  currentInstance,  // 获取当前组件实例的函数
  isInSSRComponentSetup,  // 判断是否在服务端渲染组件设置中的函数
  setCurrentInstance,  // 设置当前组件实例的函数
} from './component'

import { callWithAsyncErrorHandling } from './errorHandling'  // 导入用于处理异步错误的函数

import { queuePostRenderEffect } from './renderer'  // 导入用于排队渲染后效果的函数

import { warn } from './warning'  // 导入用于警告的函数

import type { ObjectWatchOptionItem } from './componentOptions'  // 导入对象监听选项项类型

import { useSSRContext } from './helpers/useSsrContext'  // 导入用于获取服务端渲染上下文的函数

export type {  // 导出一些类型
  WatchHandle,  // 监听句柄类型
  WatchStopHandle,  // 监听停止句柄类型
  WatchEffect,  // 监听效果函数类型
  WatchSource,  // 监听源类型
  WatchCallback,  // 监听回调函数类型
  OnCleanup,  // 清理函数类型
} from '@vue/reactivity'

type MaybeUndefined<T, I> = I extends true ? T | undefined : T  // 如果 I 为 true,则类型可能是 T 或 undefined,否则为 T

type MapSources<T, Immediate> = {  // 定义 MapSources 类型,用于根据 Immediate 标志决定监听源的值是否可能是 undefined
  [K in keyof T]: T[K] extends WatchSource<infer V>  // 如果 T[K] 是 WatchSource 类型,则推断其值的类型 V
    ? MaybeUndefined<V, Immediate>  // 根据 Immediate 标志决定是否可能是 undefined
    : T[K] extends object  // 如果 T[K] 是对象类型
      ? MaybeUndefined<T[K], Immediate>  // 根据 Immediate 标志决定是否可能是 undefined
      : never  // 否则,类型为 never
}

export interface WatchEffectOptions extends DebuggerOptions {  // 定义 WatchEffectOptions 接口,继承自 DebuggerOptions
  flush?: 'pre' | 'post' | 'sync'  // 定义 flush 选项,可以是 'pre'、'post' 或 'sync'
}

export interface WatchOptions<Immediate = boolean> extends WatchEffectOptions {  // 定义 WatchOptions 接口,继承自 WatchEffectOptions
  immediate?: Immediate  // 定义 immediate 选项,可以是 boolean 类型
  deep?: boolean | number  // 定义 deep 选项,可以是 boolean 或 number 类型
  once?: boolean  // 定义 once 选项,可以是 boolean 类型
}

// Simple effect.
export function watchEffect(  // 定义 watchEffect 函数,用于创建一个简单的监听效果
  effect: WatchEffect,  // 接收一个监听效果函数作为参数
  options?: WatchEffectOptions,  // 可选的监听效果选项
): WatchHandle {  // 返回一个监听句柄
  return doWatch(effect, null, options)  // 调用 doWatch 函数实现监听效果
}

export function watchPostEffect(  // 定义 watchPostEffect 函数,用于创建一个渲染后执行的监听效果
  effect: WatchEffect,  // 接收一个监听效果函数作为参数
  options?: DebuggerOptions,  // 可选的调试器选项
): WatchHandle {  // 返回一个监听句柄
  // 在开发环境下,如果传入了 options,则扩展 options 对象并添加 flush: 'post' 选项,否则直接设置 flush: 'post'
  return doWatch(
    effect,
    null,
    __DEV__ ? extend({}, options as any, { flush: 'post' }) : { flush: 'post' },
  )
}

export function watchSyncEffect(  // 定义 watchSyncEffect 函数,用于创建一个同步执行的监听效果
  effect: WatchEffect,  // 接收一个监听效果函数作为参数
  options?: DebuggerOptions,  // 可选的调试器选项
): WatchHandle {  // 返回一个监听句柄
  // 在开发环境下,如果传入了 options,则扩展 options 对象并添加 flush: 'sync' 选项,否则直接设置 flush: 'sync'
  return doWatch(
    effect,
    null,
    __DEV__ ? extend({}, options as any, { flush: 'sync' }) : { flush: 'sync' },
  )
}

export type MultiWatchSources = (WatchSource<unknown> | object)[]  // 定义 MultiWatchSources 类型,表示可以监听多个源(可以是 WatchSource 或者对象)

// overload: single source + cb  // 重载:单个源 + 回调函数
export function watch<T, Immediate extends Readonly<boolean> = false>(  // 定义 watch 函数,泛型 T 表示源的类型,Immediate 表示是否立即执行
  source: WatchSource<T>,  // 接收一个监听源作为参数
  cb: WatchCallback<T, MaybeUndefined<T, Immediate>>,  // 接收一个回调函数作为参数,回调函数的参数依据 Immediate 决定是否可能是 undefined
  options?: WatchOptions<Immediate>,  // 可选的监听选项
): WatchHandle  // 返回一个监听句柄

// overload: reactive array or tuple of multiple sources + cb  // 重载:响应式数组或多个源的元组 + 回调函数
export function watch<  // 定义 watch 函数,泛型 T 表示源的类型,Immediate 表示是否立即执行
  T extends Readonly<MultiWatchSources>,  // T 为只读的 MultiWatchSources 类型(响应式数组或多个源的元组)
  Immediate extends Readonly<boolean> = false,  // Immediate 表示是否立即执行
>(
  sources: readonly [...T] | T,  // 接收一个响应式数组或多个源的元组作为参数
  cb: [T] extends [ReactiveMarker]  // 如果 sources 是响应式标记
    ? WatchCallback<T, MaybeUndefined<T, Immediate>>  // 则回调函数的参数依据 Immediate 决定是否可能是 undefined
    : WatchCallback<MapSources<T, false>, MapSources<T, Immediate>>,  // 否则,回调函数的参数依据 Immediate 决定是否可能是 undefined,并且 MapSources<T, false> 表示初始时的值,MapSources<T, Immediate> 表示变化后的值
  options?: WatchOptions<Immediate>,  // 可选的监听选项
): WatchHandle  // 返回一个监听句柄

// overload: array of multiple sources + cb  // 重载:多个源的数组 + 回调函数
export function watch<  // 定义 watch 函数,泛型 T 表示源的类型,Immediate 表示是否立即执行
  T extends MultiWatchSources,  // T 为 MultiWatchSources 类型(多个源的数组)
  Immediate extends Readonly<boolean> = false,  // Immediate 表示是否立即执行
>(
  sources: [...T],  // 接收一个多个源的数组作为参数
  cb: WatchCallback<MapSources<T, false>, MapSources<T, Immediate>>,  // 接收一个回调函数作为参数,回调函数的参数依据 Immediate 决定是否可能是 undefined,并且 MapSources<T, false> 表示初始时的值,MapSources<T, Immediate> 表示变化后的值
  options?: WatchOptions<Immediate>,  // 可选的监听选项
): WatchHandle  // 返回一个监听句柄

// overload: watching reactive object w/ cb  // 重载:监听响应式对象 + 回调函数
export function watch<  // 定义 watch 函数,泛型 T 表示源的类型,Immediate 表示是否立即执行
  T extends object,  // T 为对象类型
  Immediate extends Readonly<boolean> = false,  // Immediate 表示是否立即执行
>(
  source: T,  // 接收一个响应式对象作为参数
  cb: WatchCallback<T, MaybeUndefined<T, Immediate>>,  // 接收一个回调函数作为参数,回调函数的参数依据 Immediate 决定是否可能是 undefined
  options?: WatchOptions<Immediate>,  // 可选的监听选项
): WatchHandle  // 返回一个监听句柄

// implementation  // 实现
export function watch<T = any, Immediate extends Readonly<boolean> = false>(  // 定义 watch 函数,泛型 T 表示源的类型(默认为 any),Immediate 表示是否立即执行
  source: T | WatchSource<T>,  // 接收一个源或者监听源作为参数
  cb: any,  // 接收一个回调函数作为参数
  options?: WatchOptions<Immediate>,  // 可选的监听选项
): WatchHandle {  // 返回一个监听句柄
  if (__DEV__ && !isFunction(cb)) {  // 如果在开发环境下且 cb 不是函数,则发出警告
    warn(  // 发出警告
      `\`watch(fn, options?)\` signature has been moved to a separate API. ` +  // 提示 watch(fn, options?) 签名已被移动到单独的 API
        `Use \`watchEffect(fn, options?)\` instead. \`watch\` now only ` +  // 建议使用 watchEffect 替代
        `supports \`watch(source, callback, options?) signature.`,
    )
  }
  return doWatch(source as any, cb, options)  // 调用 doWatch 函数实现监听
}

function doWatch(  // 定义 doWatch 函数,用于实际的监听逻辑
  source: WatchSource | WatchSource[] | WatchEffect | object,  // 接收一个监听源、监听源数组、监听效果函数或者对象作为参数
  cb: WatchCallback | null,  // 接收一个回调函数或者 null 作为参数
  options: WatchOptions = EMPTY_OBJ,  // 可选的监听选项(默认为空对象)
): WatchHandle {  // 返回一个监听句柄
  const { immediate, deep, flush, once } = options  // 解构 options 对象,取出 immediate、deep、flush 和 once 选项

  if (__DEV__ && !cb) {  // 如果在开发环境下且 cb 为 null,则发出警告
    if (immediate !== undefined) {  // 如果 immediate 选项被设置,则发出警告
      warn(  // 发出警告
        `watch() "immediate" option is only respected when using the ` +  // 提示 immediate 选项仅在使用 watch(source, callback, options?) 签名时生效
          `watch(source, callback, options?) signature.`,
      )
    }
    if (deep !== undefined) {  // 如果 deep 选项被设置,则发出警告
      warn(  // 发出警告
        `watch() "deep" option is only respected when using the ` +  // 提示 deep 选项仅在使用 watch(source, callback, options?) 签名时生效
          `watch(source, callback, options?) signature.`,
      )
    }
    if (once !== undefined) {  // 如果 once 选项被设置,则发出警告
      warn(  // 发出警告
        `watch() "once" option is only respected when using the ` +  // 提示 once 选项仅在使用 watch(source, callback, options?) 签名时生效
          `watch(source, callback, options?) signature.`,
      )
    }
  }

  const baseWatchOptions: BaseWatchOptions = extend({}, options)  // 将 options 扩展到 baseWatchOptions 对象

  if (__DEV__) baseWatchOptions.onWarn = warn  // 如果在开发环境下,则设置 baseWatchOptions 的 onWarn 选项为 warn 函数

  // immediate watcher or watchEffect  // 立即执行的监听器或监听效果
  const runsImmediately = (cb && immediate) || (!cb && flush !== 'post')  // 如果有回调函数且设置了 immediate,则 runsImmediately 为 true;如果没有回调函数且 flush 不是 'post',则 runsImmediately 为 true
  let ssrCleanup: (() => void)[] | undefined  // 定义 ssrCleanup 数组,用于存储服务端渲染时的清理函数
  if (__SSR__ && isInSSRComponentSetup) {  // 如果在服务端渲染环境下且在组件设置中,则进行服务端渲染相关的处理
    if (flush === 'sync') {  // 如果 flush 为 'sync'
      const ctx = useSSRContext()!  // 获取服务端渲染上下文
      ssrCleanup = ctx.__watcherHandles || (ctx.__watcherHandles = [])  // 初始化 ssrCleanup 数组
    } else if (!runsImmediately) {  // 如果不立即执行
      const watchStopHandle = () => {}  // 定义一个空的 watchStopHandle 函数
      watchStopHandle.stop = NOOP  // 设置 watchStopHandle 的 stop 方法为空操作
      watchStopHandle.resume = NOOP  // 设置 watchStopHandle 的 resume 方法为空操作
      watchStopHandle.pause = NOOP  // 设置 watchStopHandle 的 pause 方法为空操作
      return watchStopHandle  // 返回 watchStopHandle
    }
  }

  const instance = currentInstance  // 获取当前组件实例
  baseWatchOptions.call = (fn, type, args) =>  // 设置 baseWatchOptions 的 call 方法,用于调用函数并处理错误
    callWithAsyncErrorHandling(fn, instance, type, args)  // 调用 callWithAsyncErrorHandling 函数处理异步错误

  // scheduler  // 调度器
  let isPre = false  // 定义 isPre 标志,表示是否为 'pre' 刷新模式
  if (flush === 'post') {  // 如果 flush 为 'post'
    baseWatchOptions.scheduler = job => {  // 设置 baseWatchOptions 的 scheduler 方法
      queuePostRenderEffect(job, instance && instance.suspense)  // 调用 queuePostRenderEffect 函数排队渲染后效果
    }
  } else if (flush !== 'sync') {  // 如果 flush 不是 'sync'
    // default: 'pre'  // 默认为 'pre'
    isPre = true  // 设置 isPre 为 true
    baseWatchOptions.scheduler = (job, isFirstRun) => {  // 设置 baseWatchOptions 的 scheduler 方法
      if (isFirstRun) {  // 如果是第一次运行
        job()  // 执行任务
      } else {  // 否则
        queueJob(job)  // 调用 queueJob 函数排队任务
      }
    }
  }

  baseWatchOptions.augmentJob = (job: SchedulerJob) => {  // 设置 baseWatchOptions 的 augmentJob 方法,用于增强调度任务
    // important: mark the job as a watcher callback so that scheduler knows  // 重要:将任务标记为监听回调,以便调度器知道
    // it is allowed to self-trigger (#1727)  // 它允许自我触发
    if (cb) {  // 如果有回调函数
      job.flags! |= SchedulerJobFlags.ALLOW_RECURSE  // 设置任务的 flags 标志为允许自我触发
    }
    if (isPre) {  // 如果为 'pre' 刷新模式
      job.flags! |= SchedulerJobFlags.PRE  // 设置任务的 flags 标志为 'pre'
      if (instance) {  // 如果有组件实例
        job.id = instance.uid  // 设置任务的 id 为组件实例的 uid
        ;(job as SchedulerJob).i = instance  // 设置任务的 i 为组件实例
      }
    }
  }

  const watchHandle = baseWatch(source, cb, baseWatchOptions)  // 调用基础的 baseWatch 函数,传入监听源、回调函数和增强后的选项,返回监听句柄

  if (__SSR__ && isInSSRComponentSetup) {  // 如果在服务端渲染环境下且在组件设置中,则进行服务端渲染相关的处理
    if (ssrCleanup) {  // 如果有 ssrCleanup 数组
      ssrCleanup.push(watchHandle)  // 将监听句柄添加到 ssrCleanup 数组
    } else if (runsImmediately) {  // 如果立即执行
      watchHandle()  // 调用监听句柄
    }
  }

  return watchHandle  // 返回监听句柄
}

// this.$watch  // this.$watch 方法
export function instanceWatch(  // 定义 instanceWatch 函数,用于组件实例的监听
  this: ComponentInternalInstance,  // 该函数的上下文为组件内部实例
  source: string | Function,  // 接收一个字符串或函数作为监听源
  value: WatchCallback | ObjectWatchOptionItem,  // 接收一个回调函数或对象监听选项项作为参数
  options?: WatchOptions,  // 可选的监听选项
): WatchHandle {  // 返回一个监听句柄
  const publicThis = this.proxy as any  // 获取组件实例的公共代理对象
  const getter = isString(source)  // 如果 source 是字符串,则创建 getter 函数
    ? source.includes('.')  // 如果字符串包含 '.'
      ? createPathGetter(publicThis, source)  // 则调用 createPathGetter 函数创建 getter 函数
      : () => publicThis[source]  // 否则,创建一个简单的 getter 函数,直接返回 publicThis[source]
    : source.bind(publicThis, publicThis)  // 如果 source 是函数,则将其绑定到 publicThis 上下文
  let cb  // 定义 cb 变量,用于存储回调函数
  if (isFunction(value)) {  // 如果 value 是函数,则直接赋值给 cb
    cb = value
  } else {  // 否则,value 是对象监听选项项
    cb = value.handler as Function  // 获取 value 中的 handler 函数并赋值给 cb
    options = value  // 将 value 赋值给 options
  }
  const reset = setCurrentInstance(this)  // 设置当前组件实例,并返回重置函数
  const res = doWatch(getter, cb.bind(publicThis), options)  // 调用 doWatch 函数实现监听,并将 getter、绑定到 publicThis 上下文的 cb 和 options 作为参数传入,返回监听句柄
  reset()  // 调用重置函数恢复组件实例状态
  return res  // 返回监听句柄
}

export function createPathGetter(ctx: any, path: string) {  // 定义 createPathGetter 函数,用于创建路径 getter 函数
  const segments = path.split('.')  // 将路径字符串按 '.' 分割成多个段
  return (): any => {  // 返回一个 getter 函数
    let cur = ctx  // 定义 cur 变量,初始值为 ctx
    for (let i = 0; i < segments.length && cur; i++) {  // 遍历 segments 数组,并且 cur 不为 null 或 undefined
      cur = cur[segments[i]]  // 逐段深入 ctx 对象,获取最终的值
    }
    return cur  // 返回最终的值
  }
}
  1. watchEffect 函数

    • 该函数用于创建一个观察者,当其依赖的数据发生变化时,会重新运行传入的效果函数 effect
    • 通过调用 doWatch 函数来实现具体的观察者逻辑。
  2. watchPostEffect 和 watchSyncEffect 函数

    • 这两个函数是 watchEffect 的变体,分别用于在渲染后和同步执行观察者效果。
    • 它们通过设置不同的 flush 选项来控制观察者的执行时机。
  3. watch 函数

    • 该函数用于创建一个观察者,监听特定的数据源 source,当数据源发生变化时,会执行回调函数 cb
    • 支持多种类型的 source,包括单个 WatchSource、多个 WatchSource 的数组或元组,以及一个响应式对象。
    • 如果在开发环境下,cb 不是函数,则会发出警告,提示使用 watchEffect
    • 通过调用 doWatch 函数来实现具体的观察者逻辑。
  4. doWatch 函数

    • 这是一个内部函数,用于处理 watch 和 watchEffect 的通用逻辑。
    • 根据不同的选项参数(如 immediatedeepflush 和 once)来配置观察者的执行行为。
    • 在服务器端渲染(SSR)环境中,处理了一些特殊的逻辑。
    • 根据 flush 选项的不同,设置了不同的调度器来控制观察者的执行时机。
    • 最后,使用 baseWatch 函数来创建观察者,并返回观察者的处理句柄 watchHandle
  5. instanceWatch 函数

    • 该函数是组件实例中使用的 watch 方法。
    • 根据传入的 source 类型(字符串或函数)创建一个 getter 函数。
    • 处理了回调函数 cb 及其选项参数。
    • 通过 setCurrentInstance 设置当前组件实例,确保在回调中可以访问组件实例的上下文。
    • 最后,调用 doWatch 函数来创建观察者,并返回观察者的处理句柄 watchHandle
  6. createPathGetter 函数

    • 用于创建一个 getter 函数,该函数根据传入的路径字符串从上下文中获取对应的值。
    • 通过路径字符串的分割,逐级访问对象的属性,最终返回结果。

相关源码

相关源码部分 源码位置packages\reactivity\src\watch.ts

虽然 watchwatchEffect 的高层级实现位于 @vue/runtime-core 包下的 apiWatch.ts 文件中,但 packages/reactivity/src/watch.ts 文件提供了支持这些功能的核心逻辑。具体来说,它可能包含了一些与 Watcher 类似的内部类或函数,用于处理副作用函数的执行、依赖关系的追踪以及响应式数据变化时的回调触发等。这意味着:

  • 副作用管理:它可以帮助管理副作用函数(即那些需要响应状态变化而重新执行的函数),确保它们能够正确地响应依赖项的变化。
  • 依赖跟踪:通过 track 函数来记录哪些响应式属性被访问了,从而建立副作用函数和响应式数据之间的联系。
  • 更新触发:当响应式数据发生变化时,使用 trigger 函数通知所有相关的副作用函数重新执行
// 源码部分
// 导入 Vue 共享工具函数
import {
  EMPTY_OBJ,        // 空对象,用于初始化默认选项
  NOOP,             // 空操作函数,用于默认情况下的占位
  hasChanged,       // 判断两个值是否发生了变化
  isArray,          // 判断一个值是否为数组
  isFunction,       // 判断一个值是否为函数
  isMap,            // 判断一个值是否为 Map
  isObject,         // 判断一个值是否为对象
  isPlainObject,    // 判断一个值是否为普通对象
  isSet,            // 判断一个值是否为 Set
  remove,           // 从数组中移除指定的元素
} from '@vue/shared'

// 导入警告函数
import { warn } from './warning'

// 导入计算属性相关的类型定义
import type { ComputedRef } from './computed'

// 导入响应式系统相关的常量
import { ReactiveFlags } from './constants'

// 导入调试选项、效果标志、调度器和响应式效果相关的类型定义和函数
import {
  type DebuggerOptions,    // 调试选项类型定义
  EffectFlags,            // 效果标志常量
  type EffectScheduler,   // 调度器类型定义
  ReactiveEffect,         // 响应式效果类
  pauseTracking,          // 暂停追踪响应式依赖
  resetTracking,          // 重置追踪响应式依赖
} from './effect'

// 导入判断响应式和浅响应式的函数
import { isReactive, isShallow } from './reactive'

// 导入判断 ref 的函数以及 Ref 类型定义
import { type Ref, isRef } from './ref'

// 导入获取当前作用域的函数
import { getCurrentScope } from './effectScope'

// 定义 WatchErrorCodes 枚举,用于错误处理
export enum WatchErrorCodes {
  WATCH_GETTER = 2,  // 监听 getter 函数时的错误码
  WATCH_CALLBACK,    // 监听回调函数时的错误码
  WATCH_CLEANUP,     // 清理函数时的错误码
}

// 定义 WatchEffect 类型,用于 watchEffect 的回调函数
export type WatchEffect = (onCleanup: OnCleanup) => void

// 定义 WatchSource 类型,用于 watch 函数的监听源
export type WatchSource<T = any> = Ref<T, any> | ComputedRef<T> | (() => T)

// 定义 WatchCallback 类型,用于 watch 函数的回调函数
export type WatchCallback<V = any, OV = any> = (
  value: V,          // 新值
  oldValue: OV,      // 旧值
  onCleanup: OnCleanup,  // 清理函数
) => any

// 定义 OnCleanup 类型,用于注册清理回调函数
export type OnCleanup = (cleanupFn: () => void) => void

// 定义 WatchOptions 接口,用于 watch 函数的配置选项
export interface WatchOptions<Immediate = boolean> extends DebuggerOptions {
  immediate?: Immediate,  // 立即触发回调函数
  deep?: boolean | number,  // 深度监听
  once?: boolean,  // 只监听一次
  scheduler?: WatchScheduler,  // 自定义调度器
  onWarn?: (msg: string, ...args: any[]) => void,  // 警告回调函数
  /**
   * @internal
   */
  augmentJob?: (job: (...args: any[]) => void) => void,  // 内部使用的作业增强函数
  /**
   * @internal
   */
  call?: (
    fn: Function | Function[],
    type: WatchErrorCodes,
    args?: unknown[],
  ) => void  // 内部使用的调用函数
}

// 定义 WatchStopHandle 类型,用于停止监听
export type WatchStopHandle = () => void

// 定义 WatchHandle 接口,用于监听的返回对象
export interface WatchHandle extends WatchStopHandle {
  pause: () => void,  // 暂停监听
  resume: () => void, // 恢复监听
  stop: () => void    // 停止监听
}

// initial value for watchers to trigger on undefined initial values
// 监听器的初始值,用于在初始值为 undefined 时触发
const INITIAL_WATCHER_VALUE = {}

// 定义 WatchScheduler 类型,用于调度监听回调函数
export type WatchScheduler = (job: () => void, isFirstRun: boolean) => void

// 用于存储清理函数的 WeakMap
const cleanupMap: WeakMap<ReactiveEffect, (() => void)[]> = new WeakMap()
// 当前活动的监听器
let activeWatcher: ReactiveEffect | undefined = undefined

/**
 * Returns the current active effect if there is one.
 * 如果存在当前活动的监听器,则返回
 */
export function getCurrentWatcher(): ReactiveEffect<any> | undefined {
  return activeWatcher
}

/**
 * Registers a cleanup callback on the current active effect. This
 * registered cleanup callback will be invoked right before the
 * associated effect re-runs.
 * 注册一个清理回调函数到当前活动的监听器上,该回调函数会在监听器重新运行前被调用。
 *
 * @param cleanupFn - The callback function to attach to the effect's cleanup.
 * @param failSilently - if `true`, will not throw warning when called without
 * an active effect.
 * @param owner - The effect that this cleanup function should be attached to.
 * By default, the current active effect.
 */
export function onWatcherCleanup(
  cleanupFn: () => void,
  failSilently = false,
  owner: ReactiveEffect | undefined = activeWatcher,
): void {
  if (owner) {
    let cleanups = cleanupMap.get(owner)
    if (!cleanups) cleanupMap.set(owner, (cleanups = []))
    cleanups.push(cleanupFn)
  } else if (__DEV__ && !failSilently) {
    warn(
      `onWatcherCleanup() was called when there was no active watcher` +
        ` to associate with.`,
    )
  }
}

// 定义 watch 函数,用于监听响应式数据的变化
export function watch(
  source: WatchSource | WatchSource[] | WatchEffect | object,  // 监听源
  cb?: WatchCallback | null,  // 回调函数
  options: WatchOptions = EMPTY_OBJ,  // 配置选项
): WatchHandle {
  const { immediate, deep, once, scheduler, augmentJob, call } = options

  // 警告无效监听源的函数
  const warnInvalidSource = (s: unknown) => {
    ;(options.onWarn || 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.`,
    )
  }

  // 对于响应式对象的处理函数
  const reactiveGetter = (source: object) => {
    // traverse will happen in wrapped getter below
    // 遍历操作会在下面的包装 getter 中进行
    if (deep) return source
    // for `deep: false | 0` or shallow reactive, only traverse root-level properties
    // 对于 `deep: false | 0` 或浅响应式对象,只遍历根级属性
    if (isShallow(source) || deep === false || deep === 0)
      return traverse(source, 1)
    // for `deep: undefined` on a reactive object, deeply traverse all properties
    // 对于 `deep: undefined` 的响应式对象,深度遍历所有属性
    return traverse(source)
  }

  let effect: ReactiveEffect  // 响应式效果实例
  let getter: () => any  // 获取监听值的函数
  let cleanup: (() => void) | undefined  // 清理函数
  let boundCleanup: typeof onWatcherCleanup  // 绑定的清理函数
  let forceTrigger = false  // 是否强制触发
  let isMultiSource = false  // 是否有多个监听源

  // 如果监听源是 ref
  if (isRef(source)) {
    getter = () => source.value  // 获取 ref 的值
    forceTrigger = isShallow(source)  // 浅 ref 强制触发
  } else if (isReactive(source)) {  // 如果监听源是响应式对象
    getter = () => reactiveGetter(source)  // 使用 reactiveGetter 获取值
    forceTrigger = 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  // 获取数组中 ref 的值
        } else if (isReactive(s)) {
          return reactiveGetter(s)  // 获取数组中响应式对象的值
        } else if (isFunction(s)) {
          return call ? call(s, WatchErrorCodes.WATCH_GETTER) : s()  // 调用数组中的函数
        } else {
          __DEV__ && warnInvalidSource(s)  // 无效监听源警告
        }
      })
  } else if (isFunction(source)) {  // 如果监听源是函数
    if (cb) {  // 如果有回调函数
      // getter with cb
      // 有回调函数的 getter
      getter = call
        ? () => call(source, WatchErrorCodes.WATCH_GETTER)
        : (source as () => any)
    } else {  // 如果没有回调函数
      // no cb -> simple effect
      // 没有回调函数 -> 简单效果
      getter = () => {
        if (cleanup) {
          pauseTracking()  // 暂停追踪响应式依赖
          try {
            cleanup()  // 调用清理函数
          } finally {
            resetTracking()  // 重置追踪响应式依赖
          }
        }
        const currentEffect = activeWatcher  // 保存当前活动的监听器
        activeWatcher = effect  // 设置当前监听器为活动监听器
        try {
          return call
            ? call(source, WatchErrorCodes.WATCH_CALLBACK, [boundCleanup])
            : source(boundCleanup)  // 调用 getter 函数并绑定清理函数
        } finally {
          activeWatcher = currentEffect  // 恢复活动监听器
        }
      }
    }
  } else {  // 如果监听源既不是 ref 也不是响应式对象,也不是数组或函数
    getter = NOOP  // 使用空操作函数作为 getter
    __DEV__ && warnInvalidSource(source)  // 无效监听源警告
  }

  // 如果需要深度监听并且有回调函数
  if (cb && deep) {
    const baseGetter = getter  // 保存基础 getter
    const depth = deep === true ? Infinity : deep  // 设置深度
    getter = () => traverse(baseGetter(), depth)  // 使用 traverse 深度遍历获取值
  }

  // 获取当前作用域
  const scope = getCurrentScope()
  // 定义停止监听的处理函数
  const watchHandle: WatchHandle = () => {
    effect.stop()  // 停止监听器
    if (scope && scope.active) {
      remove(scope.effects, effect)  // 从作用域中移除监听器
    }
  }

  // 如果只监听一次并且有回调函数
  if (once && cb) {
    const _cb = cb  // 保存原始回调函数
    cb = (...args) => {
      _cb(...args)  // 调用原始回调函数
      watchHandle()  // 停止监听
    }
  }

  // 初始化旧值
  let oldValue: any = isMultiSource
    ? new Array((source as []).length).fill(INITIAL_WATCHER_VALUE)  // 多源监听初始化旧值数组
    : INITIAL_WATCHER_VALUE  // 单源监听初始化旧值

  // 定义监听任务
  const job = (immediateFirstRun?: boolean) => {
    if (
      !(effect.flags & EffectFlags.ACTIVE) ||  // 如果监听器不活跃
      (!effect.dirty && !immediateFirstRun)  // 或者没有变化且不是立即第一次运行
    ) {
      return  // 不执行任务
    }
    if (cb) {  // 如果有回调函数
      // watch(source, cb)
      // 监听 source 并执行回调函数
      const newValue = effect.run()  // 获取新值
      if (
        deep ||  // 深度监听
        forceTrigger ||  // 强制触发
        (isMultiSource
          ? (newValue as any[]).some((v, i) => hasChanged(v, oldValue[i]))  // 多源监听时,检查是否有任何值发生变化
          : hasChanged(newValue, oldValue))  // 单源监听时,检查值是否发生变化
      ) {
        // cleanup before running cb again
        // 在重新运行回调函数前清理
        if (cleanup) {
          cleanup()  // 调用清理函数
        }
        const currentWatcher = activeWatcher  // 保存当前活动的监听器
        activeWatcher = effect  // 设置当前监听器为活动监听器
        try {
          const args = [
            newValue,  // 新值
            // pass undefined as the old value when it's changed for the first time
            // 第一次变化时,旧值传 undefined
            oldValue === INITIAL_WATCHER_VALUE
              ? undefined
              : isMultiSource && oldValue[0] === INITIAL_WATCHER_VALUE
                ? []
                : oldValue,  // 旧值
            boundCleanup,  // 绑定的清理函数
          ]
          call
            ? call(cb!, WatchErrorCodes.WATCH_CALLBACK, args)
            : // @ts-expect-error
              cb!(...args)  // 调用回调函数
          oldValue = newValue  // 更新旧值为新值
        } finally {
          activeWatcher = currentWatcher  // 恢复活动监听器
        }
      }
    } else {
      // watchEffect
      // watchEffect 监听器
      effect.run()  // 执行监听器
    }
  }

  // 如果需要增强作业
  if (augmentJob) {
    augmentJob(job)  // 增强作业
  }

  // 创建响应式效果实例
  effect = new ReactiveEffect(getter)

  // 设置调度器
  effect.scheduler = scheduler
    ? () => scheduler(job, false)
    : (job as EffectScheduler)

  // 绑定清理函数
  boundCleanup = fn => onWatcherCleanup(fn, false, effect)

  // 设置清理函数
  cleanup = effect.onStop = () => {
    const cleanups = cleanupMap.get(effect)
    if (cleanups) {
      if (call) {
        call(cleanups, WatchErrorCodes.WATCH_CLEANUP)
      } else {
        for (const cleanup of cleanups) cleanup()
      }
      cleanupMap.delete(effect)  // 从 cleanupMap 中删除该效果的清理函数
    }
  }

  // 设置调试选项
  if (__DEV__) {
    effect.onTrack = options.onTrack
    effect.onTrigger = options.onTrigger
  }

  // 初始运行
  if (cb) {
    if (immediate) {
      job(true)  // 立即执行监听任务
    } else {
      oldValue = effect.run()  // 获取初始值作为旧值
    }
  } else if (scheduler) {
    scheduler(job.bind(null, true), true)  // 使用调度器调度监听任务,立即执行
  } else {
    effect.run()  // 执行监听任务
  }

  // 绑定暂停和恢复监听的方法
  watchHandle.pause = effect.pause.bind(effect)
  watchHandle.resume = effect.resume.bind(effect)
  watchHandle.stop = watchHandle  // 停止监听的方法

  return watchHandle  // 返回监听处理对象
}

// 定义 traverse 函数,用于深度遍历响应式对象的所有属性
export function traverse(
  value: unknown,  // 监听值
  depth: number = Infinity,  // 深度,默认无限深度
  seen?: Set<unknown>,  // 已遍历值的集合
): unknown {
  if (depth <= 0 || !isObject(value) || (value as any)[ReactiveFlags.SKIP]) {
    // 如果深度小于等于 0 或者不是对象 或者对象带有 SKIP 标志
    return value  // 返回值
  }

  seen = seen || new Set()  // 初始化已遍历值的集合
  if (seen.has(value)) {
    // 如果已遍历集合中包含该值
    return value  // 返回值
  }
  seen.add(value)  // 将值添加到已遍历集合中
  depth--  // 深度减一
  if (isRef(value)) {
    traverse(value.value, depth, seen)  // 深度遍历 ref 的值
  } else if (isArray(value)) {
    for (let i = 0; i < value.length; i++) {
      traverse(value[i], depth, seen)  // 深度遍历数组中的每个元素
    }
  } else if (isSet(value) || isMap(value)) {
    value.forEach((v: any) => {
      traverse(v, depth, seen)  // 深度遍历 Set 或 Map 中的每个值
    })
  } else if (isPlainObject(value)) {
    for (const key in value) {
      traverse(value[key], depth, seen)  // 深度遍历普通对象中的每个属性
    }
    for (const key of Object.getOwnPropertySymbols(value)) {
      if (Object.prototype.propertyIsEnumerable.call(value, key)) {
        traverse(value[key as any], depth, seen)  // 深度遍历普通对象中的每个符号属性
      }
    }
  }
  return value  // 返回值
}

watch和watchEffect的对比

在 Vue 3 中,watchwatchEffect 都是用来响应数据变化的 API,但它们在使用方式和行为上有一些重要的区别:

watch

  • 显式指定依赖watch 只追踪明确侦听的数据源。它不会追踪任何在回调中访问到的东西。因此,我们可以更加精确地控制回调函数的触发时机。
  • 惰性执行watch 是惰性的,只有当被监听的数据发生变化时才会执行回调函数。
  • 提供新旧值watch 回调可以接收两个参数,分别是变化后的新值和变化前的旧值。
  • 适合复杂逻辑:由于它可以显式地定义依赖关系,并且能够获取到新旧值,因此更适合处理复杂的业务逻辑。

watchEffect

  • 自动收集依赖watchEffect 会在副作用发生期间自动追踪所有能访问到的响应式属性。这意味着只要这些属性发生变化,watchEffect 就会重新执行。
  • 立即执行watchEffect 在创建时会立即执行一次,这使得它非常适合用于需要立即执行的逻辑。
  • 不提供新旧值watchEffect 的回调不会接收新旧值作为参数。
  • 简化代码:对于简单的、依赖源和逻辑强相关的场景,watchEffect 可以使代码更简洁。

使用场景

  • 当你需要对特定的数据进行监听并且只在该数据变化时触发某些操作,或者你需要在回调中获取到新旧值时,应该选择 watch
  • 如果你希望一段代码在所有涉及的响应式数据变化时都重新运行,并且这段代码不需要区分是哪个数据导致的变化,那么 watchEffect 是更好的选择。

根据提供的源码实现,我们可以对比 watchwatchEffect 的功能、实现原理以及使用上的差异。

特性/方法watchEffectwatch
立即执行是,watchEffect 会在定义时立刻运行一次给定的效果函数,并响应式地追踪其依赖。否,除非指定了 immediate: true 选项,否则不会在定义时立即执行回调。
监听的数据源自动追踪所有在其作用域内使用的响应式数据作为依赖。明确指定要监听的一个或多个响应式数据源(如 refreactive 对象的属性)。
回调函数没有显式的回调函数,直接将副作用函数传递进去。需要提供一个回调函数来处理数据变化后的逻辑。
选项配置支持通过选项对象来设置调度模式(flush),但不支持 deepimmediate 等其他选项。提供了更丰富的选项配置,比如可以设置是否立即执行(immediate)、深度监听(deep)、仅执行一次(once)等。
调度模式默认情况下,watchEffect 在组件更新之前同步执行(即 flush: 'pre')。可以通过 flush 选项选择在什么时候触发回调:同步 (sync)、前 (pre) 或后 (post) 渲染。
清理机制当依赖项发生变化时,会自动清理旧的副作用并重新创建新的副作用。如果指定了 onInvalidate 回调,则可以在副作用被清理时执行自定义逻辑。
性能优化由于它是懒惰的,只在依赖项改变时才会重新执行,因此通常具有更好的性能表现。对于复杂的监听场景,可能会因为频繁触发而影响性能,不过可以通过配置来优化。
应用场景适用于需要对响应式状态的变化做出即时反应的场景,例如计算属性、事件监听器等。更适合用于那些希望明确控制何时响应变化的情况,比如表单验证、异步请求等。

实现原理

  • watchEffect:

    • 内部调用了 effect 函数,它会自动追踪任何在其内部访问过的响应式数据。
    • 使用了 tracktrigger 来管理依赖关系,并确保当这些依赖发生变化时能够正确地重新执行副作用函数。
    • 它利用了 doWatch 函数来进行实际的调度和执行。
  • watch:

    • 同样依赖于 doWatch 函数,但它允许开发者指定要监听的具体数据源。
    • 支持更多的选项配置,使得它可以适应更加复杂的需求。
    • 当监听的数据源发生变化时,它会根据配置决定如何以及何时触发回调函数。

综上所述,虽然两者都基于相同的底层机制工作,但它们的设计目标和服务场景有所不同,选择哪一个取决于具体的开发需求和技术考量。对于简单的、即时响应的需求,watchEffect 是个不错的选择;而对于需要精细控制的场景,则更适合使用 watch API .