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

@@ -1,12 +1,12 @@
import {
effect,
stop,
isRef,
Ref,
ComputedRef,
ReactiveEffect,
ReactiveEffectOptions,
isReactive,
ReactiveFlags
ReactiveFlags,
EffectScheduler
} from '@vue/reactivity'
import { SchedulerJob, queuePreFlushCb } from './scheduler'
import {
@@ -244,7 +244,7 @@ function doWatch(
let cleanup: () => void
let onInvalidate: InvalidateCbRegistrator = (fn: () => void) => {
cleanup = runner.options.onStop = () => {
cleanup = effect.onStop = () => {
callWithErrorHandling(fn, instance, ErrorCodes.WATCH_CLEANUP)
}
}
@@ -268,12 +268,12 @@ function doWatch(
let oldValue = isMultiSource ? [] : INITIAL_WATCHER_VALUE
const job: SchedulerJob = () => {
if (!runner.active) {
if (!effect.active) {
return
}
if (cb) {
// watch(source, cb)
const newValue = runner()
const newValue = effect.run()
if (
deep ||
forceTrigger ||
@@ -300,7 +300,7 @@ function doWatch(
}
} else {
// watchEffect
runner()
effect.run()
}
}
@@ -308,7 +308,7 @@ function doWatch(
// it is allowed to self-trigger (#1727)
job.allowRecurse = !!cb
let scheduler: ReactiveEffectOptions['scheduler']
let scheduler: EffectScheduler
if (flush === 'sync') {
scheduler = job as any // the scheduler function gets called directly
} else if (flush === 'post') {
@@ -326,32 +326,35 @@ function doWatch(
}
}
const runner = effect(getter, {
lazy: true,
onTrack,
onTrigger,
scheduler
})
const effect = new ReactiveEffect(getter, scheduler)
recordInstanceBoundEffect(runner, instance)
if (__DEV__) {
effect.onTrack = onTrack
effect.onTrigger = onTrigger
}
recordInstanceBoundEffect(effect, instance)
// initial run
if (cb) {
if (immediate) {
job()
} else {
oldValue = runner()
oldValue = effect.run()
}
} else if (flush === 'post') {
queuePostRenderEffect(runner, instance && instance.suspense)
queuePostRenderEffect(
effect.run.bind(effect),
instance && instance.suspense
)
} else {
runner()
effect.run()
}
return () => {
stop(runner)
effect.stop()
if (instance) {
remove(instance.effects!, runner)
remove(instance.effects!, effect)
}
}
}