perf(reactivity): improve reactive effect memory usage (#4001)

Based on #2345 , but with smaller API change

- Use class implementation for `ReactiveEffect`
- Switch internal creation of effects to use the class constructor
- Avoid options object allocation
- Avoid creating bound effect runner function (used in schedulers) when not necessary.
- Consumes ~17% less memory compared to last commit
- Introduces a very minor breaking change: the `scheduler` option passed to `effect` no longer receives the runner function.
This commit is contained in:
Evan You
2021-06-24 17:44:32 -04:00
parent 63a51ffcab
commit 87f69fd0bb
12 changed files with 221 additions and 208 deletions

View File

@@ -48,15 +48,13 @@ import {
flushPostFlushCbs,
invalidateJob,
flushPreFlushCbs,
SchedulerCb
SchedulerJob
} from './scheduler'
import {
effect,
stop,
ReactiveEffectOptions,
isRef,
pauseTracking,
resetTracking
resetTracking,
ReactiveEffect
} from '@vue/reactivity'
import { updateProps } from './componentProps'
import { updateSlots } from './componentSlots'
@@ -286,23 +284,6 @@ export const enum MoveType {
REORDER
}
const prodEffectOptions = {
scheduler: queueJob,
// #1801, #2043 component render effects should allow recursive updates
allowRecurse: true
}
function createDevEffectOptions(
instance: ComponentInternalInstance
): ReactiveEffectOptions {
return {
scheduler: queueJob,
allowRecurse: true,
onTrack: instance.rtc ? e => invokeArrayFns(instance.rtc!, e) : void 0,
onTrigger: instance.rtg ? e => invokeArrayFns(instance.rtg!, e) : void 0
}
}
export const queuePostRenderEffect = __FEATURE_SUSPENSE__
? queueEffectWithSuspense
: queuePostFlushCb
@@ -378,7 +359,7 @@ export const setRef = (
// null values means this is unmount and it should not overwrite another
// ref with the same key
if (value) {
;(doSet as SchedulerCb).id = -1
;(doSet as SchedulerJob).id = -1
queuePostRenderEffect(doSet, parentSuspense)
} else {
doSet()
@@ -388,7 +369,7 @@ export const setRef = (
ref.value = value
}
if (value) {
;(doSet as SchedulerCb).id = -1
;(doSet as SchedulerJob).id = -1
queuePostRenderEffect(doSet, parentSuspense)
} else {
doSet()
@@ -1394,7 +1375,7 @@ function baseCreateRenderer(
// in case the child component is also queued, remove it to avoid
// double updating the same child component in the same flush.
invalidateJob(instance.update)
// instance.update is the reactive effect runner.
// instance.update is the reactive effect.
instance.update()
}
} else {
@@ -1414,8 +1395,7 @@ function baseCreateRenderer(
isSVG,
optimized
) => {
// create reactive effect for rendering
instance.update = effect(function componentEffect() {
const componentUpdateFn = () => {
if (!instance.isMounted) {
let vnodeHook: VNodeHook | null | undefined
const { el, props } = initialVNode
@@ -1639,12 +1619,33 @@ function baseCreateRenderer(
popWarningContext()
}
}
}, __DEV__ ? createDevEffectOptions(instance) : prodEffectOptions)
}
// create reactive effect for rendering
const effect = (instance.effect = new ReactiveEffect(
componentUpdateFn,
() => queueJob(instance.update),
true /* allowRecurse */
))
const update = (instance.update = effect.run.bind(effect) as SchedulerJob)
update.id = instance.uid
// allowRecurse
// #1801, #2043 component render effects should allow recursive updates
update.allowRecurse = true
if (__DEV__) {
// @ts-ignore
instance.update.ownerInstance = instance
effect.onTrack = instance.rtc
? e => invokeArrayFns(instance.rtc!, e)
: void 0
effect.onTrigger = instance.rtg
? e => invokeArrayFns(instance.rtg!, e)
: void 0
// @ts-ignore (for scheduler)
update.ownerInstance = instance
}
update()
}
const updateComponentPreRender = (
@@ -2284,7 +2285,7 @@ function baseCreateRenderer(
unregisterHMR(instance)
}
const { bum, effects, update, subTree, um } = instance
const { bum, effect, effects, update, subTree, um } = instance
// beforeUnmount hook
if (bum) {
@@ -2299,13 +2300,15 @@ function baseCreateRenderer(
if (effects) {
for (let i = 0; i < effects.length; i++) {
stop(effects[i])
effects[i].stop()
}
}
// update may be null if a component is unmounted before its async
// setup has resolved.
if (update) {
stop(update)
if (effect) {
effect.stop()
// so that scheduler will no longer invoke it
update.active = false
unmount(subTree, instance, parentSuspense, doRemove)
}
// unmounted hook