Skip to content

Commit

Permalink
feat: scheduler in reactivity
Browse files Browse the repository at this point in the history
  • Loading branch information
LittleSound committed Mar 14, 2024
1 parent 406c750 commit dad9d0f
Show file tree
Hide file tree
Showing 8 changed files with 51 additions and 75 deletions.
5 changes: 3 additions & 2 deletions packages/reactivity/__tests__/baseWatch.spec.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import type { Scheduler, SchedulerJob } from '../src/baseWatch'
import {
BaseWatchErrorCodes,
EffectScope,
type Ref,
type SchedulerJob,
type WatchScheduler,
baseWatch,
onWatcherCleanup,
ref,
Expand All @@ -15,7 +16,7 @@ let isFlushPending = false
const resolvedPromise = /*#__PURE__*/ Promise.resolve() as Promise<any>
const nextTick = (fn?: () => any) =>
fn ? resolvedPromise.then(fn) : resolvedPromise
const scheduler: Scheduler = (job, effect, immediateFirstRun, hasCb) => {
const scheduler: WatchScheduler = (job, effect, immediateFirstRun, hasCb) => {
if (immediateFirstRun) {
!hasCb && effect.run()
} else {
Expand Down
40 changes: 4 additions & 36 deletions packages/reactivity/src/baseWatch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
} from './effect'
import { isReactive, isShallow } from './reactive'
import { type Ref, isRef } from './ref'
import { type SchedulerJob, SchedulerJobFlags } from './scheduler'

// These errors were transferred from `packages/runtime-core/src/errorHandling.ts`
// along with baseWatch to maintain code compatibility. Hence,
Expand All @@ -32,39 +33,6 @@ export enum BaseWatchErrorCodes {
WATCH_CLEANUP,
}

// TODO move to a scheduler package
enum SchedulerJobFlags {
QUEUED = 1 << 0,
PRE = 1 << 1,
/**
* Indicates whether the effect is allowed to recursively trigger itself
* when managed by the scheduler.
*
* By default, a job cannot trigger itself because some built-in method calls,
* e.g. Array.prototype.push actually performs reads as well (#1740) which
* can lead to confusing infinite loops.
* The allowed cases are component update functions and watch callbacks.
* Component update functions may update child component props, which in turn
* trigger flush: "pre" watch callbacks that mutates state that the parent
* relies on (#1801). Watch callbacks doesn't track its dependencies so if it
* triggers itself again, it's likely intentional and it is the user's
* responsibility to perform recursive state mutation that eventually
* stabilizes (#1727).
*/
ALLOW_RECURSE = 1 << 2,
DISPOSED = 1 << 3,
}

// TODO move to a scheduler package
export interface SchedulerJob extends Function {
id?: number
/**
* flags can technically be undefined, but it can still be used in bitwise
* operations just like 0.
*/
flags?: SchedulerJobFlags
}

type WatchEffect = (onCleanup: OnCleanup) => void
type WatchSource<T = any> = Ref<T> | ComputedRef<T> | (() => T)
type WatchCallback<V = any, OV = any> = (
Expand All @@ -78,15 +46,15 @@ export interface BaseWatchOptions<Immediate = boolean> extends DebuggerOptions {
immediate?: Immediate
deep?: boolean
once?: boolean
scheduler?: Scheduler
scheduler?: WatchScheduler
onError?: HandleError
onWarn?: HandleWarn
}

// initial value for watchers to trigger on undefined initial values
const INITIAL_WATCHER_VALUE = {}

export type Scheduler = (
export type WatchScheduler = (
job: SchedulerJob,
effect: ReactiveEffect,
immediateFirstRun: boolean,
Expand All @@ -95,7 +63,7 @@ export type Scheduler = (
export type HandleError = (err: unknown, type: BaseWatchErrorCodes) => void
export type HandleWarn = (msg: string, ...args: any[]) => void

const DEFAULT_SCHEDULER: Scheduler = (
const DEFAULT_SCHEDULER: WatchScheduler = (
job,
effect,
immediateFirstRun,
Expand Down
3 changes: 2 additions & 1 deletion packages/reactivity/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,5 +85,6 @@ export {
onWatcherCleanup,
BaseWatchErrorCodes,
type BaseWatchOptions,
type Scheduler,
type WatchScheduler,
} from './baseWatch'
export { type SchedulerJob, SchedulerJobFlags } from './scheduler'
30 changes: 30 additions & 0 deletions packages/reactivity/src/scheduler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
export enum SchedulerJobFlags {
QUEUED = 1 << 0,
PRE = 1 << 1,
/**
* Indicates whether the effect is allowed to recursively trigger itself
* when managed by the scheduler.
*
* By default, a job cannot trigger itself because some built-in method calls,
* e.g. Array.prototype.push actually performs reads as well (#1740) which
* can lead to confusing infinite loops.
* The allowed cases are component update functions and watch callbacks.
* Component update functions may update child component props, which in turn
* trigger flush: "pre" watch callbacks that mutates state that the parent
* relies on (#1801). Watch callbacks doesn't track its dependencies so if it
* triggers itself again, it's likely intentional and it is the user's
* responsibility to perform recursive state mutation that eventually
* stabilizes (#1727).
*/
ALLOW_RECURSE = 1 << 2,
DISPOSED = 1 << 3,
}

export interface SchedulerJob extends Function {
id?: number
/**
* flags can technically be undefined, but it can still be used in bitwise
* operations just like 0.
*/
flags?: SchedulerJobFlags
}
2 changes: 1 addition & 1 deletion packages/runtime-core/__tests__/scheduler.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { SchedulerJobFlags } from '@vue/reactivity'
import {
type SchedulerJob,
SchedulerJobFlags,
flushPostFlushCbs,
flushPreFlushCbs,
invalidateJob,
Expand Down
3 changes: 1 addition & 2 deletions packages/runtime-core/src/components/BaseTransition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,11 @@ import {
} from '../vnode'
import { warn } from '../warning'
import { isKeepAlive } from './KeepAlive'
import { toRaw } from '@vue/reactivity'
import { SchedulerJobFlags, toRaw } from '@vue/reactivity'
import { ErrorCodes, callWithAsyncErrorHandling } from '../errorHandling'
import { PatchFlags, ShapeFlags, isArray } from '@vue/shared'
import { onBeforeUnmount, onMounted } from '../apiLifecycle'
import type { RendererElement } from '../renderer'
import { SchedulerJobFlags } from '../scheduler'

type Hook<T = () => void> = T | T[]

Expand Down
2 changes: 1 addition & 1 deletion packages/runtime-core/src/renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ import {
import {
type SchedulerFactory,
type SchedulerJob,
SchedulerJobFlags,
flushPostFlushCbs,
flushPreFlushCbs,
invalidateJob,
Expand All @@ -50,6 +49,7 @@ import {
import {
EffectFlags,
ReactiveEffect,
SchedulerJobFlags,
pauseTracking,
resetTracking,
} from '@vue/reactivity'
Expand Down
41 changes: 9 additions & 32 deletions packages/runtime-core/src/scheduler.ts
Original file line number Diff line number Diff line change
@@ -1,37 +1,14 @@
import { ErrorCodes, callWithErrorHandling, handleError } from './errorHandling'
import { type Awaited, NOOP, isArray } from '@vue/shared'
import { type ComponentInternalInstance, getComponentName } from './component'
import { EffectFlags, type Scheduler } from '@vue/reactivity'

export enum SchedulerJobFlags {
QUEUED = 1 << 0,
PRE = 1 << 1,
/**
* Indicates whether the effect is allowed to recursively trigger itself
* when managed by the scheduler.
*
* By default, a job cannot trigger itself because some built-in method calls,
* e.g. Array.prototype.push actually performs reads as well (#1740) which
* can lead to confusing infinite loops.
* The allowed cases are component update functions and watch callbacks.
* Component update functions may update child component props, which in turn
* trigger flush: "pre" watch callbacks that mutates state that the parent
* relies on (#1801). Watch callbacks doesn't track its dependencies so if it
* triggers itself again, it's likely intentional and it is the user's
* responsibility to perform recursive state mutation that eventually
* stabilizes (#1727).
*/
ALLOW_RECURSE = 1 << 2,
DISPOSED = 1 << 3,
}

export interface SchedulerJob extends Function {
id?: number
/**
* flags can technically be undefined, but it can still be used in bitwise
* operations just like 0.
*/
flags?: SchedulerJobFlags
import {
type SchedulerJob as BaseSchedulerJob,
EffectFlags,
SchedulerJobFlags,
type WatchScheduler,
} from '@vue/reactivity'

export interface SchedulerJob extends BaseSchedulerJob {
/**
* Attached by renderer.ts when setting up a component's render effect
* Used to obtain component information when reporting max recursive updates.
Expand Down Expand Up @@ -301,7 +278,7 @@ function checkRecursiveUpdates(seen: CountMap, fn: SchedulerJob) {

export type SchedulerFactory = (
instance: ComponentInternalInstance | null,
) => Scheduler
) => WatchScheduler

export const createSyncScheduler: SchedulerFactory =
instance => (job, effect, immediateFirstRun, hasCb) => {
Expand Down

0 comments on commit dad9d0f

Please sign in to comment.