Vue3 computed和watch源码分析

其他教程   发布日期:2024年05月08日   浏览次数:360

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

    computed

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

    1. // packages/reactivity/src/computed.ts
    2. export function computed<T>(
    3. getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>,
    4. debugOptions?: DebuggerOptions,
    5. isSSR = false
    6. ) {
    7. let getter: ComputedGetter<T>
    8. let setter: ComputedSetter<T>
    9. const onlyGetter = isFunction(getterOrOptions)
    10. if (onlyGetter) {
    11. getter = getterOrOptions
    12. setter = __DEV__
    13. ? () => {
    14. console.warn('Write operation failed: computed value is readonly')
    15. }
    16. : NOOP
    17. } else {
    18. getter = getterOrOptions.get
    19. setter = getterOrOptions.set
    20. }
    21. // new ComputedRefImpl
    22. const cRef = new ComputedRefImpl(getter, setter, onlyGetter || !setter, isSSR)
    23. if (__DEV__ && debugOptions && !isSSR) {
    24. cRef.effect.onTrack = debugOptions.onTrack
    25. cRef.effect.onTrigger = debugOptions.onTrigger
    26. }
    27. // 返回ComputedRefImpl实例
    28. return cRef as any
    29. }

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

    ComputedRefImpl

    1. // packages/reactivity/src/computed.ts
    2. export class ComputedRefImpl<T> {
    3. public dep?: Dep = undefined // 存储effect的集合
    4. private _value!: T
    5. public readonly effect: ReactiveEffect<T>
    6. public readonly __v_isRef = true
    7. public readonly [ReactiveFlags.IS_READONLY]: boolean = false
    8. public _dirty = true // 是否需要重新更新value
    9. public _cacheable: boolean
    10. constructor(
    11. getter: ComputedGetter<T>,
    12. private readonly _setter: ComputedSetter<T>,
    13. isReadonly: boolean,
    14. isSSR: boolean
    15. ) {
    16. // 创建effect
    17. this.effect = new ReactiveEffect(getter, () => {
    18. // 调度器执行 重新赋值_dirty为true
    19. if (!this._dirty) {
    20. this._dirty = true
    21. // 触发effect
    22. triggerRefValue(this)
    23. }
    24. })
    25. // 用于区分effect是否是computed
    26. this.effect.computed = this
    27. this.effect.active = this._cacheable = !isSSR
    28. this[ReactiveFlags.IS_READONLY] = isReadonly
    29. }
    30. get value() {
    31. // the computed ref may get wrapped by other proxies e.g. readonly() #3376
    32. // computed ref可能被其他代理包装,例如readonly() #3376
    33. // 通过toRaw()获取原始值
    34. const self = toRaw(this)
    35. // 收集effect
    36. trackRefValue(self)
    37. // 如果是脏的,重新执行effect.run(),并且将_dirty设置为false
    38. if (self._dirty || !self._cacheable) {
    39. self._dirty = false
    40. // run()方法会执行getter方法 值会被缓存到self._value
    41. self._value = self.effect.run()!
    42. }
    43. return self._value
    44. }
    45. set value(newValue: T) {
    46. this._setter(newValue)
    47. }
    48. }

    可以看到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(此前章节也有提到):

    1. // packages/reactivity/src/effect.ts
    2. export function triggerEffects(
    3. dep: Dep | ReactiveEffect[],
    4. debuggerEventExtraInfo?: DebuggerEventExtraInfo
    5. ) {
    6. // spread into array for stabilization
    7. const effects = isArray(dep) ? dep : [...dep]
    8. // computed的effect会先执行
    9. // 防止render获取computed值得时候_dirty还没有置为true
    10. for (const effect of effects) {
    11. if (effect.computed) {
    12. triggerEffect(effect, debuggerEventExtraInfo)
    13. }
    14. }
    15. for (const effect of effects) {
    16. if (!effect.computed) {
    17. triggerEffect(effect, debuggerEventExtraInfo)
    18. }
    19. }
    20. }

    watch

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

    1. // packages/runtime-core/src/apiWatch.ts
    2. export function watch<T = any, Immediate extends Readonly<boolean> = false>(
    3. source: T | WatchSource<T>,
    4. cb: any,
    5. options?: WatchOptions<Immediate>
    6. ): WatchStopHandle {
    7. if (__DEV__ && !isFunction(cb)) {
    8. warn(...)
    9. }
    10. // watch 具体实现
    11. return doWatch(source as any, cb, options)
    12. }
    1. function doWatch(
    2. source: WatchSource | WatchSource[] | WatchEffect | object,
    3. cb: WatchCallback | null,
    4. { immediate, deep, flush, onTrack, onTrigger }: WatchOptions = EMPTY_OBJ
    5. ): WatchStopHandle {
    6. if (__DEV__ && !cb) {
    7. ...
    8. }
    9. const warnInvalidSource = (s: unknown) => {
    10. warn(...)
    11. }
    12. const instance =
    13. getCurrentScope() === currentInstance?.scope ? currentInstance : null
    14. // const instance = currentInstance
    15. let getter: () => any
    16. let forceTrigger = false
    17. let isMultiSource = false
    18. // 根据不同source 创建不同的getter函数
    19. // getter 函数与computed的getter函数作用类似
    20. if (isRef(source)) {
    21. getter = () => source.value
    22. forceTrigger = isShallow(source)
    23. } else if (isReactive(source)) {
    24. // source是reactive对象时 自动开启deep=true
    25. getter = () => source
    26. deep = true
    27. } else if (isArray(source)) {
    28. isMultiSource = true
    29. forceTrigger = source.some(s => isReactive(s) || isShallow(s))
    30. getter = () =>
    31. source.map(s => {
    32. if (isRef(s)) {
    33. return s.value
    34. } else if (isReactive(s)) {
    35. return traverse(s)
    36. } else if (isFunction(s)) {
    37. return callWithErrorHandling(s, instance, ErrorCodes.WATCH_GETTER)
    38. } else {
    39. __DEV__ && warnInvalidSource(s)
    40. }
    41. })
    42. } else if (isFunction(source)) {
    43. if (cb) {
    44. // getter with cb
    45. getter = () =>
    46. callWithErrorHandling(source, instance, ErrorCodes.WATCH_GETTER)
    47. } else {
    48. // no cb -> simple effect
    49. getter = () => {
    50. if (instance && instance.isUnmounted) {
    51. return
    52. }
    53. if (cleanup) {
    54. cleanup()
    55. }
    56. return callWithAsyncErrorHandling(
    57. source,
    58. instance,
    59. ErrorCodes.WATCH_CALLBACK,
    60. [onCleanup]
    61. )
    62. }
    63. }
    64. } else {
    65. getter = NOOP
    66. __DEV__ && warnInvalidSource(source)
    67. }
    68. // 2.x array mutation watch compat
    69. // 兼容vue2
    70. if (__COMPAT__ && cb && !deep) {
    71. const baseGetter = getter
    72. getter = () => {
    73. const val = baseGetter()
    74. if (
    75. isArray(val) &&
    76. checkCompatEnabled(DeprecationTypes.WATCH_ARRAY, instance)
    77. ) {
    78. traverse(val)
    79. }
    80. return val
    81. }
    82. }
    83. // 深度监听
    84. if (cb && deep) {
    85. const baseGetter = getter
    86. // traverse会递归遍历对象的所有属性 以达到深度监听的目的
    87. getter = () => traverse(baseGetter())
    88. }
    89. let cleanup: () => void
    90. // watch回调的第三个参数 可以用此注册一个cleanup函数 会在下一次watch cb调用前执行
    91. // 常用于竞态问题的处理
    92. let onCleanup: OnCleanup = (fn: () => void) => {
    93. cleanup = effect.onStop = () => {
    94. callWithErrorHandling(fn, instance, ErrorCodes.WATCH_CLEANUP)
    95. }
    96. }
    97. // in SSR there is no need to setup an actual effect, and it should be noop
    98. // unless it's eager or sync flush
    99. let ssrCleanup: (() => void)[] | undefined
    100. if (__SSR__ && isInSSRComponentSetup) {
    101. // ssr处理 ...
    102. }
    103. // oldValue 声明 多个source监听则初始化为数组
    104. let oldValue: any = isMultiSource
    105. ? new Array((source as []).length).fill(INITIAL_WATCHER_VALUE)
    106. : INITIAL_WATCHER_VALUE
    107. // 调度器调用时执行
    108. const job: SchedulerJob = () => {
    109. if (!effect.active) {
    110. return
    111. }
    112. if (cb) {
    113. // watch(source, cb)
    114. // 获取newValue
    115. const newValue = effect.run()
    116. if (
    117. deep ||
    118. forceTrigger ||
    119. (isMultiSource
    120. ? (newValue as any[]).some((v, i) =>
    121. hasChanged(v, (oldValue as any[])[i])
    122. )
    123. : hasChanged(newValue, oldValue)) ||
    124. (__COMPAT__ &&
    125. isArray(newValue) &&
    126. isCompatEnabled(DeprecationTypes.WATCH_ARRAY, instance))
    127. ) {
    128. // cleanup before running cb again
    129. if (cleanup) {
    130. // 执行onCleanup传过来的函数
    131. cleanup()
    132. }
    133. // 调用cb 参数为newValue、oldValue、onCleanup
    134. callWithAsyncErrorHandling(cb, instance, ErrorCodes.WATCH_CALLBACK, [
    135. newValue,
    136. // pass undefined as the old value when it's changed for the first time
    137. oldValue === INITIAL_WATCHER_VALUE
    138. ? undefined
    139. : isMultiSource && oldValue[0] === INITIAL_WATCHER_VALUE
    140. ? []
    141. : oldValue,
    142. onCleanup
    143. ])
    144. // 更新oldValue
    145. oldValue = newValue
    146. }
    147. } else {
    148. // watchEffect
    149. effect.run()
    150. }
    151. }
    152. // important: mark the job as a watcher callback so that scheduler knows
    153. // it is allowed to self-trigger (#1727)
    154. job.allowRecurse = !!cb
    155. let scheduler: EffectScheduler
    156. if (flush === 'sync') {
    157. // 同步更新 即每次响应式数据改变都会回调一次cb 通常不使用
    158. scheduler = job as any // the scheduler function gets called directly
    159. } else if (flush === 'post') {
    160. // job放入pendingPostFlushCbs队列中
    161. // pendingPostFlushCbs队列会在queue队列执行完毕后执行 函数更新effect通常会放在queue队列中
    162. // 所以pendingPostFlushCbs队列执行时组件已经更新完毕
    163. scheduler = () => queuePostRenderEffect(job, instance && instance.suspense)
    164. } else {
    165. // default: 'pre'
    166. job.pre = true
    167. if (instance) job.id = instance.uid
    168. // 默认异步更新 关于异步更新会和nextTick放在一起详细讲解
    169. scheduler = () => queueJob(job)
    170. }
    171. // 创建effect effect.run的时候建立effect与getter内响应式数据的关系
    172. const effect = new ReactiveEffect(getter, scheduler)
    173. if (__DEV__) {
    174. effect.onTrack = onTrack
    175. effect.onTrigger = onTrigger
    176. }
    177. // initial run
    178. if (cb) {
    179. if (immediate) {
    180. // 立马执行一次job
    181. job()
    182. } else {
    183. // 否则执行effect.run() 会执行getter 获取oldValue
    184. oldValue = effect.run()
    185. }
    186. } else if (flush === 'post') {
    187. queuePostRenderEffect(
    188. effect.run.bind(effect),
    189. instance && instance.suspense
    190. )
    191. } else {
    192. effect.run()
    193. }
    194. // 返回一个取消监听的函数
    195. const unwatch = () => {
    196. effect.stop()
    197. if (instance && instance.scope) {
    198. remove(instance.scope.effects!, effect)
    199. }
    200. }
    201. if (__SSR__ && ssrCleanup) ssrCleanup.push(unwatch)
    202. return unwatch
    203. }

    以上就是Vue3 computed和watch源码分析的详细内容,更多关于Vue3 computed和watch源码分析的资料请关注九品源码其它相关文章!