Skip to content

perf(reactivity): Array shift() should only trigger effect once #9056

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 138 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
138 commits
Select commit Hold shift + click to select a range
f485ea0
perf: calculate embedded `computed()` on-demand
johnsoncodehk May 13, 2022
208e59c
fix: computed effect dons't transfer
johnsoncodehk May 13, 2022
4a3e554
fix: revert effects trigger order
johnsoncodehk May 13, 2022
ed871c2
refactor: remove `computedToAskDirty` arg from `trigger()`
johnsoncodehk May 13, 2022
fbf214a
chore: use `hasChanged()` instead of `!==`
johnsoncodehk May 13, 2022
399f155
perf: reduce `triggerRefValue()` triggered
johnsoncodehk May 14, 2022
0e0c44e
perf: avoid duplicate trigger effect to deps
johnsoncodehk Jan 11, 2023
7768d43
fix: avoid track unrelated effects
johnsoncodehk Feb 2, 2023
a353eac
chore: make `_computedsToAskDirty` private
johnsoncodehk Feb 2, 2023
014dcd5
fix: jest -> vitest
johnsoncodehk Feb 2, 2023
eb0f8fa
fix: urgent assessment edge case
johnsoncodehk Apr 5, 2023
f3a0071
feat: more purposeful test
johnsoncodehk Apr 5, 2023
998c400
chore: add test for effect() on-demand trigger
johnsoncodehk Apr 6, 2023
b5527a1
Revert "chore: add test for effect() on-demand trigger"
johnsoncodehk Apr 6, 2023
3e0c2af
wip: on-demand effect()
johnsoncodehk Apr 6, 2023
62dc5ee
fix: ref newVal arg incorrect
johnsoncodehk Apr 7, 2023
e34619f
Merge branch 'computed-ondemand' into effect-ondemand
johnsoncodehk Apr 7, 2023
8895117
release: v3.3.0-alpha.9
haoqunjiang Apr 7, 2023
909afcc
fix: fixed effect for tests
johnsoncodehk Apr 7, 2023
da6263c
refactor: implement simplify
johnsoncodehk Apr 7, 2023
d2fd264
Revert "release: v3.3.0-alpha.9"
haoqunjiang Apr 7, 2023
f4d345d
Merge remote-tracking branch 'upstream/main' into computed-ondemand
johnsoncodehk Apr 8, 2023
866a3ee
Merge branch 'computed-ondemand' into effect-ondemand
johnsoncodehk Apr 8, 2023
0a16e82
Merge branch 'main' into pr/5912
johnsoncodehk Jul 31, 2023
6c9f058
chore: naming
johnsoncodehk Jul 31, 2023
66bcc3e
chore: with "s"
johnsoncodehk Jul 31, 2023
06f813e
Merge branch 'pr/5912' into pr/8043
johnsoncodehk Jul 31, 2023
debddde
chore: naming
johnsoncodehk Jul 31, 2023
ba66880
chore: undo format
johnsoncodehk Jul 31, 2023
cb081f6
chore: `_c` -> `deferredComputed`
johnsoncodehk Jul 31, 2023
7d21edf
Merge branch 'pr/5912' into pr/8043
johnsoncodehk Jul 31, 2023
76e2e6e
chore: `_c` -> `deferredComputed`
johnsoncodehk Jul 31, 2023
de4e436
chore: expressiveness
johnsoncodehk Jul 31, 2023
6c13a30
refactor: reuse `effectTrackDepth`
johnsoncodehk Jul 31, 2023
ef2a6e3
refactor: remove unneeded `pauseTracking()`, `resetTracking()`
johnsoncodehk Jul 31, 2023
7bc1069
chore: sort in one line
johnsoncodehk Jul 31, 2023
d77f842
Merge branch 'pr/5912' into pr/8043
johnsoncodehk Jul 31, 2023
f0512d6
chore: sort in one line
johnsoncodehk Jul 31, 2023
7e2ac74
chore: naming
johnsoncodehk Jul 31, 2023
e98d329
chore: import type
johnsoncodehk Jul 31, 2023
02805c5
Merge branch 'pr/5912' into pr/8043
johnsoncodehk Jul 31, 2023
d5595cb
chore: narrow `pauseTracking()`
johnsoncodehk Aug 1, 2023
01c7b80
Merge branch 'pr/5912' into pr/8043
johnsoncodehk Aug 1, 2023
44cbb5e
perf: cache dep indexes
johnsoncodehk Aug 14, 2023
8a3c786
perf: redo `length >= 2`
johnsoncodehk Aug 14, 2023
76da327
chore: hoist `triggerRefValue` for readability
johnsoncodehk Aug 14, 2023
9002b93
chore: always clear dep indexes cache to avoid memory leak
johnsoncodehk Aug 14, 2023
0f2c388
chore: less type hack
johnsoncodehk Aug 14, 2023
3b912e8
Merge branch 'main' into pr/5912
johnsoncodehk Aug 26, 2023
f8ff93a
chore: format
johnsoncodehk Aug 26, 2023
17342d5
refactor: abstract deferred computeds into ReactiveEffect
johnsoncodehk Aug 26, 2023
0194f02
fix: fixes _dirty updating for deferredComputed
johnsoncodehk Aug 26, 2023
16dc2b8
chore: revert deferredComputed
johnsoncodehk Aug 26, 2023
1b666b9
chore: format
johnsoncodehk Aug 26, 2023
bf89e57
perf: more efficient `watch()`
johnsoncodehk Aug 26, 2023
09e3512
chore(effect): use allow function
johnsoncodehk Aug 26, 2023
25578fd
chore(effect): release _deferredComputeds earlier
johnsoncodehk Aug 26, 2023
4b72c94
refactor(effect): make `scheduler` required
johnsoncodehk Aug 26, 2023
0257e69
refactor(effect): dirty -> applyDirty()
johnsoncodehk Aug 27, 2023
37d5c03
chore(effect): applyDirty() -> dirty, _dirty
johnsoncodehk Aug 27, 2023
1269644
chore(effect): add triggerEffectCallbacks()
johnsoncodehk Aug 27, 2023
07da2dd
chore(effect): triggerEffectCallbacks -> scheduleEffectCallbacks
johnsoncodehk Aug 27, 2023
891d937
chore(effect): fix syntax
johnsoncodehk Aug 27, 2023
3350c24
perf(reactivity): Array `shift()` should only trigger effect once
johnsoncodehk Aug 27, 2023
c187177
refactor(effect): de-abstract `scheduled`
johnsoncodehk Aug 28, 2023
d3517e0
Merge branch 'computed-ondemand' into array-effect
johnsoncodehk Aug 28, 2023
8f58ea3
chore(effect): mark dirty by default
johnsoncodehk Aug 28, 2023
770b2de
refactor(effect): abstract `dirty` setter
johnsoncodehk Aug 28, 2023
e0ef714
Merge branch 'computed-ondemand' into array-effect
johnsoncodehk Aug 28, 2023
e5e895d
fix(effect): revert updates for watch apis
johnsoncodehk Aug 28, 2023
3a89f4d
Merge branch 'computed-ondemand' into array-effect
johnsoncodehk Aug 28, 2023
3fc6349
chore: remove unneeded change
johnsoncodehk Aug 28, 2023
3821687
fix(computed): polyfill `_dirty`
johnsoncodehk Aug 28, 2023
a4b8bd2
Merge branch 'computed-ondemand' into array-effect
johnsoncodehk Aug 28, 2023
eb360e6
perf(runtime-core): re-render on deps changed
johnsoncodehk Aug 28, 2023
128b1eb
chore: format
johnsoncodehk Aug 28, 2023
a8f2bba
fix: computed causes render twice at the beginning
johnsoncodehk Aug 28, 2023
3aaa3af
chore: format
johnsoncodehk Aug 28, 2023
72c3312
Merge branch 'computed-ondemand' into renderer-ondemand
johnsoncodehk Aug 28, 2023
c7567d0
Merge branch 'computed-ondemand' into array-effect
johnsoncodehk Aug 28, 2023
1be6c50
fix: multiple computed triggers render multiple times
johnsoncodehk Aug 29, 2023
d2a5706
fix: computed getter trigger effect repeatedly
johnsoncodehk Aug 29, 2023
13c9a2c
fix: avoid computed side effects causing re-rendering
johnsoncodehk Aug 29, 2023
53db76c
chore: _mode -> _
johnsoncodehk Aug 29, 2023
e76b42c
chore: format
johnsoncodehk Aug 29, 2023
35f0ddd
chore: remove computed.init
johnsoncodehk Aug 29, 2023
914f62d
Merge branch 'computed-ondemand' into renderer-ondemand
johnsoncodehk Aug 29, 2023
e83221a
Merge branch 'computed-ondemand' into array-effect
johnsoncodehk Aug 29, 2023
e605560
refactor: TriggerReason -> TriggerType
johnsoncodehk Aug 29, 2023
fe8d996
chore: use resetDirty
johnsoncodehk Aug 29, 2023
9ab4eb1
chore: format
johnsoncodehk Aug 29, 2023
942be5d
chore: format
johnsoncodehk Aug 29, 2023
b1bd1de
fix: don't reset hard reset dirty for computed
johnsoncodehk Aug 29, 2023
ee05427
fix: computed deps update should re-trigger effects
johnsoncodehk Aug 29, 2023
99af87b
refactor(effect): add dirty setter
johnsoncodehk Aug 29, 2023
a181339
fix: computed should allow recursion dirty trigger
johnsoncodehk Aug 29, 2023
8084d74
fix: avoid tree shaking `deferredComputed.value`
johnsoncodehk Aug 30, 2023
194f8a8
perf: reuse `_depIndexes`
johnsoncodehk Aug 30, 2023
d4bf9ad
Merge branch 'computed-ondemand' into array-effect
johnsoncodehk Aug 30, 2023
85a7f22
Merge branch 'computed-ondemand' into renderer-ondemand
johnsoncodehk Aug 30, 2023
f620aa9
chore: fix tests
johnsoncodehk Aug 30, 2023
254d923
Merge branch 'computed-ondemand' into array-effect
johnsoncodehk Aug 30, 2023
8f8f384
Squashed commit of the following:
johnsoncodehk Aug 30, 2023
c02f650
perf: faster computed effect spread
johnsoncodehk Sep 1, 2023
8094661
Merge branch 'computed-ondemand' into array-effect
johnsoncodehk Sep 1, 2023
e74cd43
chore: format
johnsoncodehk Sep 1, 2023
fd1c9be
Merge branch 'computed-ondemand' into array-effect
johnsoncodehk Sep 1, 2023
9214ff4
Merge branch 'computed-ondemand' into renderer-ondemand
johnsoncodehk Sep 1, 2023
f5b0331
refactor: remove deferredComputed arg
johnsoncodehk Sep 1, 2023
9e64219
fix: fix vant test
johnsoncodehk Sep 1, 2023
b43c494
chore: fix triggerRefValue newValue for dev
johnsoncodehk Sep 1, 2023
c522871
chore: remove _alwaysAcceptComputedValueUpdated
johnsoncodehk Sep 1, 2023
5bd18ef
chore: less changes
johnsoncodehk Sep 1, 2023
b8c0a72
chore: less changes
johnsoncodehk Sep 1, 2023
e41763c
Merge branch 'computed-ondemand' into array-effect
johnsoncodehk Sep 1, 2023
857f60e
chore: remove unneeded `deps.includes`
johnsoncodehk Sep 1, 2023
174626c
refactor: reduce `dep` arg for triggerEffect()
johnsoncodehk Sep 1, 2023
d73ba7a
chore: move `_valueMaybeDirty = false`
johnsoncodehk Sep 1, 2023
80ea250
fix: remove `_valueMaybeDirty` for fix vuetify test
johnsoncodehk Sep 1, 2023
bc85a96
Merge branch 'computed-ondemand' into array-effect
johnsoncodehk Sep 1, 2023
e339def
refactor: abstract triggerType
johnsoncodehk Sep 1, 2023
f3625b0
chore: fix deferredComputed
johnsoncodehk Sep 1, 2023
f41bc9b
feat: redo apiWatch
johnsoncodehk Sep 1, 2023
09d6f05
fix: continuous effects cause computed dirty race condition
johnsoncodehk Sep 2, 2023
576be5f
fix: render triggers twice
johnsoncodehk Sep 2, 2023
7058975
refactor: remove queueEffectCbs
johnsoncodehk Sep 2, 2023
b3f502e
chore: DepsMaybeDirty -> ComputedValueMaybeDirty
johnsoncodehk Sep 3, 2023
a1cb22a
fix: computed scheduler should always trigger
johnsoncodehk Sep 3, 2023
bf85f9a
refactor: remove computed hack
johnsoncodehk Sep 3, 2023
f9bf306
Merge branch 'computed-ondemand' into array-effect
johnsoncodehk Sep 7, 2023
ab25229
Update deferredComputed.ts
johnsoncodehk Sep 7, 2023
3f5c373
make new logic working for `watch()`
johnsoncodehk Sep 7, 2023
1591ce1
update test result
johnsoncodehk Sep 7, 2023
eec0a78
sync #9056
johnsoncodehk Sep 7, 2023
b5eba14
Merge branch 'computed-ondemand' into array-effect
johnsoncodehk Sep 7, 2023
01e21d2
update test comment
johnsoncodehk Sep 7, 2023
0bdacad
chore: remove scheduleEffectCallbacks
johnsoncodehk Sep 7, 2023
3b0c4a7
combine if & while
johnsoncodehk Sep 8, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 90 additions & 1 deletion packages/reactivity/__tests__/computed.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ describe('reactivity/computed', () => {
// mutate n
n.value++
// on the 2nd run, plusOne.value should have already updated.
expect(plusOneValues).toMatchObject([1, 2, 2])
expect(plusOneValues).toMatchObject([1, 2])
})

it('should warn if trying to set a readonly computed', () => {
Expand Down Expand Up @@ -288,4 +288,93 @@ describe('reactivity/computed', () => {
oldValue: 2
})
})

it('chained computed value on-demand trigger', () => {
const minSpy = vi.fn()
const hourSpy = vi.fn()

const sec = ref(0)
const min = computed(() => {
minSpy()
return Math.floor(sec.value / 60)
})
const hour = computed(() => {
hourSpy()
return Math.floor(min.value / 60)
})

for (sec.value = 0; sec.value < 1000; sec.value++) {
hour.value
}

expect(minSpy).toHaveBeenCalledTimes(1000)
expect(hourSpy).toHaveBeenCalledTimes(17)
})

it('effect callback on-demand trigger', () => {
const effectSpy = vi.fn()

const sec = ref(0)
const min = computed(() => {
return Math.floor(sec.value / 60)
})
const hour = computed(() => {
return Math.floor(min.value / 60)
})

effect(() => {
effectSpy()
min.value
hour.value
})

for (sec.value = 0; sec.value < 1000; sec.value++) {}

expect(effectSpy).toHaveBeenCalledTimes(17)
})

it('chained computed value urgent assessment edge case', () => {
const cSpy = vi.fn()

const a = ref<null | { v: number }>({
v: 1
})
const b = computed(() => {
return a.value
})
const c = computed(() => {
cSpy()
return b.value?.v
})
const d = computed(() => {
if (b.value) {
return c.value
}
return 0
})

d.value
a.value!.v = 2
a.value = null
d.value
expect(cSpy).toHaveBeenCalledTimes(1)
})

test('should not continuous effects cause computed dirty race condition', () => {
const fnSpy = vi.fn()
const v = ref(1)
const c = computed(() => v.value)

effect(() => {
c.value
})
effect(() => {
c.value
fnSpy()
})

expect(fnSpy).toBeCalledTimes(1)
v.value = 2
expect(fnSpy).toBeCalledTimes(2)
})
})
4 changes: 2 additions & 2 deletions packages/reactivity/__tests__/effect.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -558,8 +558,8 @@ describe('reactivity/effect', () => {
expect(output.fx2).toBe(1 + 3 + 3)
expect(fx1Spy).toHaveBeenCalledTimes(1)

// Invoked twice due to change of fx1.
expect(fx2Spy).toHaveBeenCalledTimes(2)
// Invoked once even change of fx1.
expect(fx2Spy).toHaveBeenCalledTimes(1)

fx1Spy.mockClear()
fx2Spy.mockClear()
Expand Down
14 changes: 14 additions & 0 deletions packages/reactivity/__tests__/reactiveArray.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,20 @@ describe('reactivity/reactive/Array', () => {
expect(fn).toHaveBeenCalledTimes(1)
})

test('shift on Array should only trigger dependency once', () => {
const arr = reactive([1, 2, 3])
const fn = vi.fn()
effect(() => {
for (let i = 0; i < arr.length; i++) {
arr[i]
}
fn()
})
expect(fn).toHaveBeenCalledTimes(1)
arr.shift()
expect(fn).toHaveBeenCalledTimes(2)
})

test('add existing index on Array should not trigger length dependency', () => {
const array = new Array(3)
const observed = reactive(array)
Expand Down
6 changes: 5 additions & 1 deletion packages/reactivity/src/baseHandlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ import {
trigger,
ITERATE_KEY,
pauseTracking,
resetTracking
resetTracking,
pauseScheduling,
resetScheduling
} from './effect'
import {
isObject,
Expand Down Expand Up @@ -71,7 +73,9 @@ function createArrayInstrumentations() {
;(['push', 'pop', 'shift', 'unshift', 'splice'] as const).forEach(key => {
instrumentations[key] = function (this: unknown[], ...args: unknown[]) {
pauseTracking()
pauseScheduling()
const res = (toRaw(this) as any)[key].apply(this, args)
resetScheduling()
resetTracking()
return res
}
Expand Down
34 changes: 25 additions & 9 deletions packages/reactivity/src/computed.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { DebuggerOptions, ReactiveEffect } from './effect'
import { Ref, trackRefValue, triggerRefValue } from './ref'
import { isFunction, NOOP } from '@vue/shared'
import { hasChanged, isFunction, NOOP } from '@vue/shared'
import { ReactiveFlags, toRaw } from './reactive'
import { Dep } from './dep'
import { DirtyLevels } from './operations'

declare const ComputedRefSymbol: unique symbol

Expand Down Expand Up @@ -32,7 +33,7 @@ export class ComputedRefImpl<T> {
public readonly __v_isRef = true
public readonly [ReactiveFlags.IS_READONLY]: boolean = false

public _dirty = true
public _scheduled = false
public _cacheable: boolean

constructor(
Expand All @@ -42,9 +43,9 @@ export class ComputedRefImpl<T> {
isSSR: boolean
) {
this.effect = new ReactiveEffect(getter, () => {
if (!this._dirty) {
this._dirty = true
triggerRefValue(this)
if (!this._scheduled) {
this._scheduled = true
triggerRefValue(this, DirtyLevels.ComputedValueMaybeDirty)
}
})
this.effect.computed = this
Expand All @@ -55,17 +56,32 @@ export class ComputedRefImpl<T> {
get value() {
// the computed ref may get wrapped by other proxies e.g. readonly() #3376
const self = toRaw(this)
trackRefValue(self)
if (self._dirty || !self._cacheable) {
self._dirty = false
self._value = self.effect.run()!
trackRefValue(self, self)
if (!self._cacheable || self.effect.dirty) {
const newValue = self.effect.run()!
const changed = hasChanged(self._value, newValue)
self._value = newValue
if (changed) {
triggerRefValue(self, DirtyLevels.ComputedValueDirty)
}
self._scheduled = false
}
return self._value
}

set value(newValue: T) {
this._setter(newValue)
}

// #region polyfill _dirty to backward compatibility for Vue <= 3.3
get _dirty() {
return this.effect.dirty
}

set _dirty(v) {
this.effect.dirty = v
}
// #endregion
}

/**
Expand Down
51 changes: 28 additions & 23 deletions packages/reactivity/src/deferredComputed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,33 +38,38 @@ class DeferredComputedRefImpl<T> {
let compareTarget: any
let hasCompareTarget = false
let scheduled = false
this.effect = new ReactiveEffect(getter, (computedTrigger?: boolean) => {
if (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)
this.effect = new ReactiveEffect(
getter,
(onScheduled, computedTrigger?: boolean) => {
if (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 instanceof DeferredComputedRefImpl) {
e.scheduler(onScheduled, true /* computedTrigger */)
}
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 instanceof DeferredComputedRefImpl) {
e.scheduler!(true /* computedTrigger */)
}
}
this._dirty = true
}
this._dirty = true
})
)
this.effect.computed = this as any
}

Expand Down
8 changes: 7 additions & 1 deletion packages/reactivity/src/dep.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { ComputedRefImpl } from './computed'
import { ReactiveEffect, trackOpBit } from './effect'

export type Dep = Set<ReactiveEffect> & TrackedMarkers
Expand All @@ -16,12 +17,17 @@ type TrackedMarkers = {
* newTracked
*/
n: number
computed?: ComputedRefImpl<any>
}

export const createDep = (effects?: ReactiveEffect[]): Dep => {
export const createDep = (
effects?: ReactiveEffect[],
computed?: ComputedRefImpl<any>
): Dep => {
const dep = new Set<ReactiveEffect>(effects) as Dep
dep.w = 0
dep.n = 0
dep.computed = computed
return dep
}

Expand Down
Loading