Computed et watch sont souvent interrogés sur leurs différences dans les entretiens, jetons donc un coup d'œil à leur implémentation spécifique à partir de l'implémentation du code source
// packages/reactivity/src/computed.ts export function computed<T>( getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>, debugOptions?: DebuggerOptions, isSSR = false ) { let getter: ComputedGetter<T> let setter: ComputedSetter<T> const onlyGetter = isFunction(getterOrOptions) if (onlyGetter) { getter = getterOrOptions setter = __DEV__ ? () => { console.warn('Write operation failed: computed value is readonly') } : NOOP } else { getter = getterOrOptions.get setter = getterOrOptions.set } // new ComputedRefImpl const cRef = new ComputedRefImpl(getter, setter, onlyGetter || !setter, isSSR) if (__DEV__ && debugOptions && !isSSR) { cRef.effect.onTrack = debugOptions.onTrack cRef.effect.onTrigger = debugOptions.onTrigger } // 返回ComputedRefImpl实例 return cRef as any }
Vous pouvez voir que calculé en interne ne traite d'abord que les getters et les setters, puis un nouveau ComputedRefImpl est renvoyé. Si vous connaissez l'implémentation de l'API ref, vous pouvez constater que leurs implémentations ont de nombreuses similitudes
// packages/reactivity/src/computed.ts export class ComputedRefImpl<T> { public dep?: Dep = undefined // 存储effect的集合 private _value!: T public readonly effect: ReactiveEffect<T> public readonly __v_isRef = true public readonly [ReactiveFlags.IS_READONLY]: boolean = false public _dirty = true // 是否需要重新更新value public _cacheable: boolean constructor( getter: ComputedGetter<T>, private readonly _setter: ComputedSetter<T>, isReadonly: boolean, isSSR: boolean ) { // 创建effect this.effect = new ReactiveEffect(getter, () => { // 调度器执行 重新赋值_dirty为true if (!this._dirty) { this._dirty = true // 触发effect triggerRefValue(this) } }) // 用于区分effect是否是computed this.effect.computed = this this.effect.active = this._cacheable = !isSSR this[ReactiveFlags.IS_READONLY] = isReadonly } get value() { // the computed ref may get wrapped by other proxies e.g. readonly() #3376 // computed ref可能被其他代理包装,例如readonly() #3376 // 通过toRaw()获取原始值 const self = toRaw(this) // 收集effect trackRefValue(self) // 如果是脏的,重新执行effect.run(),并且将_dirty设置为false if (self._dirty || !self._cacheable) { self._dirty = false // run()方法会执行getter方法 值会被缓存到self._value self._value = self.effect.run()! } return self._value } set value(newValue: T) { this._setter(newValue) } }
Vous pouvez voir que l'implémentation get de ComputedRefImplget est fondamentalement la même que l'implémentation get. de ref (ceux qui ne sont pas familiers avec l'implémentation de ref Veuillez consulter le chapitre précédent), la seule différence est le jugement de la valeur _dirty. C'est ce que nous disons souvent que calculé mettra en cache la valeur. la valeur doit être mise à jour ?
Vous pouvez voir que dans le constructeur calculé, une relation entre un getter et ses données réactives internes sera établie. C'est la même que la relation entre notre fonction de mise à jour de composant et les données réactives, donc les données réactives liées au getter. est modifié, le planificateur correspondant à l'effet getter sera déclenché ici, _dirty sera défini sur true et l'effet collecté sera exécuté (c'est généralement l'effet de la mise à jour de la fonction collectée dans le get), puis le La fonction de mise à jour de la fonction sera exécutée. , le get calculé sera à nouveau déclenché. À ce moment, dirty a été défini sur true, le getter sera réexécuté pour obtenir la nouvelle valeur de retour et la valeur sera mise en cache dans _vlaue.
So Computed comporte deux couches de traitement réactif. Une couche est la relation entre la valeur calculée et l'effet de la fonction (similaire à l'implémentation de ref), et l'autre couche est le getter calculé et les données réactives. . relation.
Remarque : Si vous êtes suffisamment prudent, vous constaterez qu'il peut y avoir un problème de séquence entre le déclenchement de l'effet de la fonction de mise à jour de la fonction et le déclenchement de l'effet getter calculé. S'il existe des données réactives a qui non seulement existent dans le getter, mais sont également accessibles plus tôt que le getter dans la fonction render, alors l'effet de la fonction de mise à jour dans le dépôt correspondant à a sera collecté plus tôt que l'effet du getter. Si a est Change, l'effet de la fonction de mise à jour sera exécuté en premier, puis lorsque la fonction de rendu accédera à Computed.value, elle constatera que _dirty est toujours faux, car l'effet du getter n'a pas été exécuté, donc il sera toujours l'ancienne valeur à ce moment-là. La solution à cela dans vue3 est que lors de l'exécution des effets, les effets correspondant aux calculés seront exécutés en premier (également mentionné dans le chapitre précédent) :
// packages/reactivity/src/effect.ts export function triggerEffects( dep: Dep | ReactiveEffect[], debuggerEventExtraInfo?: DebuggerEventExtraInfo ) { // spread into array for stabilization const effects = isArray(dep) ? dep : [...dep] // computed的effect会先执行 // 防止render获取computed值得时候_dirty还没有置为true for (const effect of effects) { if (effect.computed) { triggerEffect(effect, debuggerEventExtraInfo) } } for (const effect of effects) { if (!effect.computed) { triggerEffect(effect, debuggerEventExtraInfo) } } }
watch est plus simple que calculé, car il suffit d'établir des getters et réactivité La relation entre les données, il suffit d'appeler le rappel transmis par l'utilisateur lorsque les données réactives changent et de transmettre les anciennes et les nouvelles valeurs
// packages/runtime-core/src/apiWatch.ts export 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 具体实现 return doWatch(source as any, cb, options) }
function doWatch( source: WatchSource | WatchSource[] | WatchEffect | object, cb: WatchCallback | null, { immediate, deep, flush, onTrack, onTrigger }: WatchOptions = EMPTY_OBJ ): WatchStopHandle { if (__DEV__ && !cb) { ... } const warnInvalidSource = (s: unknown) => { warn(...) } const instance = getCurrentScope() === currentInstance?.scope ? currentInstance : null // const instance = currentInstance let getter: () => any let forceTrigger = false let isMultiSource = false // 根据不同source 创建不同的getter函数 // getter 函数与computed的getter函数作用类似 if (isRef(source)) { getter = () => source.value forceTrigger = isShallow(source) } else if (isReactive(source)) { // source是reactive对象时 自动开启deep=true 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) } // 2.x array mutation watch compat // 兼容vue2 if (__COMPAT__ && cb && !deep) { const baseGetter = getter getter = () => { const val = baseGetter() if ( isArray(val) && checkCompatEnabled(DeprecationTypes.WATCH_ARRAY, instance) ) { traverse(val) } return val } } // 深度监听 if (cb && deep) { const baseGetter = getter // traverse会递归遍历对象的所有属性 以达到深度监听的目的 getter = () => traverse(baseGetter()) } let cleanup: () => void // watch回调的第三个参数 可以用此注册一个cleanup函数 会在下一次watch cb调用前执行 // 常用于竞态问题的处理 let onCleanup: OnCleanup = (fn: () => void) => { cleanup = effect.onStop = () => { callWithErrorHandling(fn, instance, ErrorCodes.WATCH_CLEANUP) } } // in SSR there is no need to setup an actual effect, and it should be noop // unless it's eager or sync flush let ssrCleanup: (() => void)[] | undefined if (__SSR__ && isInSSRComponentSetup) { // ssr处理 ... } // oldValue 声明 多个source监听则初始化为数组 let oldValue: any = isMultiSource ? new Array((source as []).length).fill(INITIAL_WATCHER_VALUE) : INITIAL_WATCHER_VALUE // 调度器调用时执行 const job: SchedulerJob = () => { if (!effect.active) { return } if (cb) { // watch(source, cb) // 获取newValue const newValue = effect.run() if ( deep || forceTrigger || (isMultiSource ? (newValue as any[]).some((v, i) => hasChanged(v, (oldValue as any[])[i]) ) : hasChanged(newValue, oldValue)) || (__COMPAT__ && isArray(newValue) && isCompatEnabled(DeprecationTypes.WATCH_ARRAY, instance)) ) { // cleanup before running cb again if (cleanup) { // 执行onCleanup传过来的函数 cleanup() } // 调用cb 参数为newValue、oldValue、onCleanup callWithAsyncErrorHandling(cb, instance, ErrorCodes.WATCH_CALLBACK, [ newValue, // pass undefined as the old value when it's changed for the first time oldValue === INITIAL_WATCHER_VALUE ? undefined : isMultiSource && oldValue[0] === INITIAL_WATCHER_VALUE ? [] : oldValue, onCleanup ]) // 更新oldValue oldValue = newValue } } else { // watchEffect effect.run() } } // important: mark the job as a watcher callback so that scheduler knows // it is allowed to self-trigger (#1727) job.allowRecurse = !!cb let scheduler: EffectScheduler if (flush === 'sync') { // 同步更新 即每次响应式数据改变都会回调一次cb 通常不使用 scheduler = job as any // the scheduler function gets called directly } else if (flush === 'post') { // job放入pendingPostFlushCbs队列中 // pendingPostFlushCbs队列会在queue队列执行完毕后执行 函数更新effect通常会放在queue队列中 // 所以pendingPostFlushCbs队列执行时组件已经更新完毕 scheduler = () => queuePostRenderEffect(job, instance && instance.suspense) } else { // default: 'pre' job.pre = true if (instance) job.id = instance.uid // 默认异步更新 关于异步更新会和nextTick放在一起详细讲解 scheduler = () => queueJob(job) } // 创建effect effect.run的时候建立effect与getter内响应式数据的关系 const effect = new ReactiveEffect(getter, scheduler) if (__DEV__) { effect.onTrack = onTrack effect.onTrigger = onTrigger } // initial run if (cb) { if (immediate) { // 立马执行一次job job() } else { // 否则执行effect.run() 会执行getter 获取oldValue oldValue = effect.run() } } else if (flush === 'post') { queuePostRenderEffect( effect.run.bind(effect), instance && instance.suspense ) } else { effect.run() } // 返回一个取消监听的函数 const unwatch = () => { effect.stop() if (instance && instance.scope) { remove(instance.scope.effects!, effect) } } if (__SSR__ && ssrCleanup) ssrCleanup.push(unwatch) return unwatch }
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!