diff --git a/packages/reactivity/__tests__/computed.spec.ts b/packages/reactivity/__tests__/computed.spec.ts index 34aec01d3e9..a3efc8c854a 100644 --- a/packages/reactivity/__tests__/computed.spec.ts +++ b/packages/reactivity/__tests__/computed.spec.ts @@ -5,7 +5,6 @@ import { ref, WritableComputedRef, isReadonly, - setComputedScheduler, DebuggerEvent, toRaw, TrackOpTypes, @@ -273,218 +272,4 @@ describe('reactivity/computed', () => { oldValue: 2 }) }) - - describe('with scheduler', () => { - // a simple scheduler similar to the main Vue scheduler - const tick = Promise.resolve() - const queue: any[] = [] - let queued = false - - const schedule = (fn: any) => { - queue.push(fn) - if (!queued) { - queued = true - tick.then(flush) - } - } - - const flush = () => { - for (let i = 0; i < queue.length; i++) { - queue[i]() - } - queue.length = 0 - queued = false - } - - beforeEach(() => { - setComputedScheduler(schedule) - }) - - afterEach(() => { - setComputedScheduler(undefined) - }) - - test('should only trigger once on multiple mutations', async () => { - const src = ref(0) - const c = computed(() => src.value) - const spy = jest.fn() - effect(() => { - spy(c.value) - }) - expect(spy).toHaveBeenCalledTimes(1) - src.value = 1 - src.value = 2 - src.value = 3 - // not called yet - expect(spy).toHaveBeenCalledTimes(1) - await tick - // should only trigger once - expect(spy).toHaveBeenCalledTimes(2) - expect(spy).toHaveBeenCalledWith(c.value) - }) - - test('should not trigger if value did not change', async () => { - const src = ref(0) - const c = computed(() => src.value % 2) - const spy = jest.fn() - effect(() => { - spy(c.value) - }) - expect(spy).toHaveBeenCalledTimes(1) - src.value = 1 - src.value = 2 - - await tick - // should not trigger - expect(spy).toHaveBeenCalledTimes(1) - - src.value = 3 - src.value = 4 - src.value = 5 - await tick - // should trigger because latest value changes - expect(spy).toHaveBeenCalledTimes(2) - }) - - test('chained computed trigger', async () => { - const effectSpy = jest.fn() - const c1Spy = jest.fn() - const c2Spy = jest.fn() - - const src = ref(0) - const c1 = computed(() => { - c1Spy() - return src.value % 2 - }) - const c2 = computed(() => { - c2Spy() - return c1.value + 1 - }) - - effect(() => { - effectSpy(c2.value) - }) - - expect(c1Spy).toHaveBeenCalledTimes(1) - expect(c2Spy).toHaveBeenCalledTimes(1) - expect(effectSpy).toHaveBeenCalledTimes(1) - - src.value = 1 - await tick - expect(c1Spy).toHaveBeenCalledTimes(2) - expect(c2Spy).toHaveBeenCalledTimes(2) - expect(effectSpy).toHaveBeenCalledTimes(2) - }) - - test('chained computed avoid re-compute', async () => { - const effectSpy = jest.fn() - const c1Spy = jest.fn() - const c2Spy = jest.fn() - - const src = ref(0) - const c1 = computed(() => { - c1Spy() - return src.value % 2 - }) - const c2 = computed(() => { - c2Spy() - return c1.value + 1 - }) - - effect(() => { - effectSpy(c2.value) - }) - - expect(effectSpy).toHaveBeenCalledTimes(1) - src.value = 2 - src.value = 4 - src.value = 6 - await tick - // c1 should re-compute once. - expect(c1Spy).toHaveBeenCalledTimes(2) - // c2 should not have to re-compute because c1 did not change. - expect(c2Spy).toHaveBeenCalledTimes(1) - // effect should not trigger because c2 did not change. - expect(effectSpy).toHaveBeenCalledTimes(1) - }) - - test('chained computed value invalidation', async () => { - const effectSpy = jest.fn() - const c1Spy = jest.fn() - const c2Spy = jest.fn() - - const src = ref(0) - const c1 = computed(() => { - c1Spy() - return src.value % 2 - }) - const c2 = computed(() => { - c2Spy() - return c1.value + 1 - }) - - effect(() => { - effectSpy(c2.value) - }) - - expect(effectSpy).toHaveBeenCalledTimes(1) - expect(effectSpy).toHaveBeenCalledWith(1) - expect(c2.value).toBe(1) - - expect(c1Spy).toHaveBeenCalledTimes(1) - expect(c2Spy).toHaveBeenCalledTimes(1) - - src.value = 1 - // value should be available sync - expect(c2.value).toBe(2) - expect(c2Spy).toHaveBeenCalledTimes(2) - }) - - test('sync access of invalidated chained computed should not prevent final effect from running', async () => { - const effectSpy = jest.fn() - const c1Spy = jest.fn() - const c2Spy = jest.fn() - - const src = ref(0) - const c1 = computed(() => { - c1Spy() - return src.value % 2 - }) - const c2 = computed(() => { - c2Spy() - return c1.value + 1 - }) - - effect(() => { - effectSpy(c2.value) - }) - expect(effectSpy).toHaveBeenCalledTimes(1) - - src.value = 1 - // sync access c2 - c2.value - await tick - expect(effectSpy).toHaveBeenCalledTimes(2) - }) - - test('should not compute if deactivated before scheduler is called', async () => { - const c1Spy = jest.fn() - const src = ref(0) - const c1 = computed(() => { - c1Spy() - return src.value % 2 - }) - effect(() => c1.value) - expect(c1Spy).toHaveBeenCalledTimes(1) - - // schedule stop - schedule(() => { - c1.effect.stop() - }) - // trigger - src.value++ - await tick - expect(c1Spy).toHaveBeenCalledTimes(1) - }) - }) }) diff --git a/packages/reactivity/src/computed.ts b/packages/reactivity/src/computed.ts index dec3a833c57..4c984d3d65a 100644 --- a/packages/reactivity/src/computed.ts +++ b/packages/reactivity/src/computed.ts @@ -20,16 +20,6 @@ export interface WritableComputedOptions { set: ComputedSetter } -type ComputedScheduler = (fn: () => void) => void -let scheduler: ComputedScheduler | undefined - -/** - * Set a scheduler for deferring computed computations - */ -export const setComputedScheduler = (s: ComputedScheduler | undefined) => { - scheduler = s -} - class ComputedRefImpl { public dep?: Dep = undefined @@ -45,55 +35,24 @@ class ComputedRefImpl { private readonly _setter: ComputedSetter, isReadonly: boolean ) { - let compareTarget: any - let hasCompareTarget = false - let scheduled = false - this.effect = new ReactiveEffect(getter, (computedTrigger?: boolean) => { - if (scheduler && this.dep) { - if (computedTrigger) { - compareTarget = this._value - hasCompareTarget = true - } else if (!scheduled) { - const valueToCompare = hasCompareTarget ? compareTarget : this._value - scheduled = true - hasCompareTarget = false - scheduler(() => { - if (this.effect.active && this._get() !== valueToCompare) { - triggerRefValue(this) - } - scheduled = false - }) - } - // chained upstream computeds are notified synchronously to ensure - // value invalidation in case of sync access; normal effects are - // deferred to be triggered in scheduler. - for (const e of this.dep) { - if (e.computed) { - e.scheduler!(true /* computedTrigger */) - } - } - } + this.effect = new ReactiveEffect(getter, () => { if (!this._dirty) { this._dirty = true - if (!scheduler) triggerRefValue(this) + triggerRefValue(this) } }) - this.effect.computed = true this[ReactiveFlags.IS_READONLY] = isReadonly } - private _get() { - if (this._dirty) { - this._dirty = false - return (this._value = this.effect.run()!) - } - return this._value - } - get value() { - trackRefValue(this) // the computed ref may get wrapped by other proxies e.g. readonly() #3376 - return toRaw(this)._get() + const self = toRaw(this) + trackRefValue(self) + if (self._dirty) { + self._dirty = false + self._value = self.effect.run()! + } + return self._value } set value(newValue: T) { diff --git a/packages/reactivity/src/index.ts b/packages/reactivity/src/index.ts index 776e4cedc53..7b62094c524 100644 --- a/packages/reactivity/src/index.ts +++ b/packages/reactivity/src/index.ts @@ -30,7 +30,6 @@ export { } from './reactive' export { computed, - setComputedScheduler, ComputedRef, WritableComputedRef, WritableComputedOptions, diff --git a/packages/runtime-core/__tests__/rendererComponent.spec.ts b/packages/runtime-core/__tests__/rendererComponent.spec.ts index 09e3cb997e3..c389e1e8354 100644 --- a/packages/runtime-core/__tests__/rendererComponent.spec.ts +++ b/packages/runtime-core/__tests__/rendererComponent.spec.ts @@ -10,8 +10,7 @@ import { inject, Ref, watch, - SetupContext, - computed + SetupContext } from '@vue/runtime-test' describe('renderer: component', () => { @@ -325,36 +324,4 @@ describe('renderer: component', () => { expect(serializeInner(root)).toBe(``) expect(ids).toEqual([ids[0], ids[0] + 1, ids[0] + 2]) }) - - test('computed that did not change should not trigger re-render', async () => { - const src = ref(0) - const c = computed(() => src.value % 2) - const spy = jest.fn() - const App = { - render() { - spy() - return c.value - } - } - - const root = nodeOps.createElement('div') - render(h(App), root) - expect(serializeInner(root)).toBe(`0`) - expect(spy).toHaveBeenCalledTimes(1) - - // verify it updates - src.value = 1 - src.value = 2 - src.value = 3 - await nextTick() - expect(serializeInner(root)).toBe(`1`) - expect(spy).toHaveBeenCalledTimes(2) // should only update once - - // verify it updates - src.value = 4 - src.value = 5 - await nextTick() - expect(serializeInner(root)).toBe(`1`) - expect(spy).toHaveBeenCalledTimes(2) // should not need to update - }) }) diff --git a/packages/runtime-core/src/scheduler.ts b/packages/runtime-core/src/scheduler.ts index 78219736931..b917a151cca 100644 --- a/packages/runtime-core/src/scheduler.ts +++ b/packages/runtime-core/src/scheduler.ts @@ -2,10 +2,6 @@ import { ErrorCodes, callWithErrorHandling } from './errorHandling' import { isArray } from '@vue/shared' import { ComponentInternalInstance, getComponentName } from './component' import { warn } from './warning' -import { setComputedScheduler } from '@vue/reactivity' - -// set scheduler for computed -setComputedScheduler(queueJob) export interface SchedulerJob extends Function { id?: number