apiWatch.ts 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. import {
  2. type WatchOptions as BaseWatchOptions,
  3. type DebuggerOptions,
  4. type ReactiveMarker,
  5. type WatchCallback,
  6. type WatchEffect,
  7. type WatchHandle,
  8. type WatchSource,
  9. watch as baseWatch,
  10. } from '@vue/reactivity'
  11. import { type SchedulerJob, SchedulerJobFlags, queueJob } from './scheduler'
  12. import { EMPTY_OBJ, NOOP, extend, isFunction, isString } from '@vue/shared'
  13. import {
  14. type ComponentInternalInstance,
  15. currentInstance,
  16. isInSSRComponentSetup,
  17. setCurrentInstance,
  18. } from './component'
  19. import { callWithAsyncErrorHandling } from './errorHandling'
  20. import { queuePostRenderEffect } from './renderer'
  21. import { warn } from './warning'
  22. import type { ObjectWatchOptionItem } from './componentOptions'
  23. import { useSSRContext } from './helpers/useSsrContext'
  24. export type {
  25. WatchHandle,
  26. WatchStopHandle,
  27. WatchEffect,
  28. WatchSource,
  29. WatchCallback,
  30. OnCleanup,
  31. } from '@vue/reactivity'
  32. type MaybeUndefined<T, I> = I extends true ? T | undefined : T
  33. type MapSources<T, Immediate> = {
  34. [K in keyof T]: T[K] extends WatchSource<infer V>
  35. ? MaybeUndefined<V, Immediate>
  36. : T[K] extends object
  37. ? MaybeUndefined<T[K], Immediate>
  38. : never
  39. }
  40. export interface WatchEffectOptions extends DebuggerOptions {
  41. flush?: 'pre' | 'post' | 'sync'
  42. }
  43. export interface WatchOptions<Immediate = boolean> extends WatchEffectOptions {
  44. immediate?: Immediate
  45. deep?: boolean | number
  46. once?: boolean
  47. }
  48. // Simple effect.
  49. export function watchEffect(
  50. effect: WatchEffect,
  51. options?: WatchEffectOptions,
  52. ): WatchHandle {
  53. return doWatch(effect, null, options)
  54. }
  55. export function watchPostEffect(
  56. effect: WatchEffect,
  57. options?: DebuggerOptions,
  58. ): WatchHandle {
  59. return doWatch(
  60. effect,
  61. null,
  62. __DEV__ ? extend({}, options as any, { flush: 'post' }) : { flush: 'post' },
  63. )
  64. }
  65. export function watchSyncEffect(
  66. effect: WatchEffect,
  67. options?: DebuggerOptions,
  68. ): WatchHandle {
  69. return doWatch(
  70. effect,
  71. null,
  72. __DEV__ ? extend({}, options as any, { flush: 'sync' }) : { flush: 'sync' },
  73. )
  74. }
  75. export type MultiWatchSources = (WatchSource<unknown> | object)[]
  76. // overload: single source + cb
  77. export function watch<T, Immediate extends Readonly<boolean> = false>(
  78. source: WatchSource<T>,
  79. cb: WatchCallback<T, MaybeUndefined<T, Immediate>>,
  80. options?: WatchOptions<Immediate>,
  81. ): WatchHandle
  82. // overload: reactive array or tuple of multiple sources + cb
  83. export function watch<
  84. T extends Readonly<MultiWatchSources>,
  85. Immediate extends Readonly<boolean> = false,
  86. >(
  87. sources: readonly [...T] | T,
  88. cb: [T] extends [ReactiveMarker]
  89. ? WatchCallback<T, MaybeUndefined<T, Immediate>>
  90. : WatchCallback<MapSources<T, false>, MapSources<T, Immediate>>,
  91. options?: WatchOptions<Immediate>,
  92. ): WatchHandle
  93. // overload: array of multiple sources + cb
  94. export function watch<
  95. T extends MultiWatchSources,
  96. Immediate extends Readonly<boolean> = false,
  97. >(
  98. sources: [...T],
  99. cb: WatchCallback<MapSources<T, false>, MapSources<T, Immediate>>,
  100. options?: WatchOptions<Immediate>,
  101. ): WatchHandle
  102. // overload: watching reactive object w/ cb
  103. export function watch<
  104. T extends object,
  105. Immediate extends Readonly<boolean> = false,
  106. >(
  107. source: T,
  108. cb: WatchCallback<T, MaybeUndefined<T, Immediate>>,
  109. options?: WatchOptions<Immediate>,
  110. ): WatchHandle
  111. // implementation
  112. export function watch<T = any, Immediate extends Readonly<boolean> = false>(
  113. source: T | WatchSource<T>,
  114. cb: any,
  115. options?: WatchOptions<Immediate>,
  116. ): WatchHandle {
  117. if (__DEV__ && !isFunction(cb)) {
  118. warn(
  119. `\`watch(fn, options?)\` signature has been moved to a separate API. ` +
  120. `Use \`watchEffect(fn, options?)\` instead. \`watch\` now only ` +
  121. `supports \`watch(source, cb, options?) signature.`,
  122. )
  123. }
  124. return doWatch(source as any, cb, options)
  125. }
  126. function doWatch(
  127. source: WatchSource | WatchSource[] | WatchEffect | object,
  128. cb: WatchCallback | null,
  129. options: WatchOptions = EMPTY_OBJ,
  130. ): WatchHandle {
  131. const { immediate, deep, flush, once } = options
  132. if (__DEV__ && !cb) {
  133. if (immediate !== undefined) {
  134. warn(
  135. `watch() "immediate" option is only respected when using the ` +
  136. `watch(source, callback, options?) signature.`,
  137. )
  138. }
  139. if (deep !== undefined) {
  140. warn(
  141. `watch() "deep" option is only respected when using the ` +
  142. `watch(source, callback, options?) signature.`,
  143. )
  144. }
  145. if (once !== undefined) {
  146. warn(
  147. `watch() "once" option is only respected when using the ` +
  148. `watch(source, callback, options?) signature.`,
  149. )
  150. }
  151. }
  152. const baseWatchOptions: BaseWatchOptions = extend({}, options)
  153. if (__DEV__) baseWatchOptions.onWarn = warn
  154. // immediate watcher or watchEffect
  155. const runsImmediately = (cb && immediate) || (!cb && flush !== 'post')
  156. let ssrCleanup: (() => void)[] | undefined
  157. if (__SSR__ && isInSSRComponentSetup) {
  158. if (flush === 'sync') {
  159. const ctx = useSSRContext()!
  160. ssrCleanup = ctx.__watcherHandles || (ctx.__watcherHandles = [])
  161. } else if (!runsImmediately) {
  162. const watchStopHandle = () => {}
  163. watchStopHandle.stop = NOOP
  164. watchStopHandle.resume = NOOP
  165. watchStopHandle.pause = NOOP
  166. return watchStopHandle
  167. }
  168. }
  169. const instance = currentInstance
  170. baseWatchOptions.call = (fn, type, args) =>
  171. callWithAsyncErrorHandling(fn, instance, type, args)
  172. // scheduler
  173. let isPre = false
  174. if (flush === 'post') {
  175. baseWatchOptions.scheduler = job => {
  176. queuePostRenderEffect(job, instance && instance.suspense)
  177. }
  178. } else if (flush !== 'sync') {
  179. // default: 'pre'
  180. isPre = true
  181. baseWatchOptions.scheduler = (job, isFirstRun) => {
  182. if (isFirstRun) {
  183. job()
  184. } else {
  185. queueJob(job)
  186. }
  187. }
  188. }
  189. baseWatchOptions.augmentJob = (job: SchedulerJob) => {
  190. // important: mark the job as a watcher callback so that scheduler knows
  191. // it is allowed to self-trigger (#1727)
  192. if (cb) {
  193. job.flags! |= SchedulerJobFlags.ALLOW_RECURSE
  194. }
  195. if (isPre) {
  196. job.flags! |= SchedulerJobFlags.PRE
  197. if (instance) {
  198. job.id = instance.uid
  199. ;(job as SchedulerJob).i = instance
  200. }
  201. }
  202. }
  203. const watchHandle = baseWatch(source, cb, baseWatchOptions)
  204. if (__SSR__ && isInSSRComponentSetup) {
  205. if (ssrCleanup) {
  206. ssrCleanup.push(watchHandle)
  207. } else if (runsImmediately) {
  208. watchHandle()
  209. }
  210. }
  211. return watchHandle
  212. }
  213. // this.$watch
  214. export function instanceWatch(
  215. this: ComponentInternalInstance,
  216. source: string | Function,
  217. value: WatchCallback | ObjectWatchOptionItem,
  218. options?: WatchOptions,
  219. ): WatchHandle {
  220. const publicThis = this.proxy as any
  221. const getter = isString(source)
  222. ? source.includes('.')
  223. ? createPathGetter(publicThis, source)
  224. : () => publicThis[source]
  225. : source.bind(publicThis, publicThis)
  226. let cb
  227. if (isFunction(value)) {
  228. cb = value
  229. } else {
  230. cb = value.handler as Function
  231. options = value
  232. }
  233. const reset = setCurrentInstance(this)
  234. const res = doWatch(getter, cb.bind(publicThis), options)
  235. reset()
  236. return res
  237. }
  238. export function createPathGetter(ctx: any, path: string) {
  239. const segments = path.split('.')
  240. return (): any => {
  241. let cur = ctx
  242. for (let i = 0; i < segments.length && cur; i++) {
  243. cur = cur[segments[i]]
  244. }
  245. return cur
  246. }
  247. }