refactor: ensure watcher callbacks are deferred
This commit is contained in:
parent
9b50a5abb9
commit
bf38fea313
@ -44,7 +44,9 @@ export interface MountedComponent<D = Data, P = Data> extends Component {
|
|||||||
destroyed?(): void
|
destroyed?(): void
|
||||||
|
|
||||||
_updateHandle: Autorun
|
_updateHandle: Autorun
|
||||||
|
_queueJob: ((fn: () => void) => void)
|
||||||
$forceUpdate: () => void
|
$forceUpdate: () => void
|
||||||
|
$nextTick: (fn: () => void) => Promise<any>
|
||||||
|
|
||||||
_self: MountedComponent<D, P> // on proxies only
|
_self: MountedComponent<D, P> // on proxies only
|
||||||
}
|
}
|
||||||
@ -68,6 +70,7 @@ export class Component {
|
|||||||
public $options: any
|
public $options: any
|
||||||
public $proxy: any = null
|
public $proxy: any = null
|
||||||
public $forceUpdate: (() => void) | null = null
|
public $forceUpdate: (() => void) | null = null
|
||||||
|
public $nextTick: ((fn: () => void) => Promise<any>) | null = null
|
||||||
|
|
||||||
public _rawData: Data | null = null
|
public _rawData: Data | null = null
|
||||||
public _computedGetters: Record<string, ComputedGetter> | null = null
|
public _computedGetters: Record<string, ComputedGetter> | null = null
|
||||||
@ -76,6 +79,7 @@ export class Component {
|
|||||||
public _destroyed: boolean = false
|
public _destroyed: boolean = false
|
||||||
public _events: { [event: string]: Function[] | null } | null = null
|
public _events: { [event: string]: Function[] | null } | null = null
|
||||||
public _updateHandle: Autorun | null = null
|
public _updateHandle: Autorun | null = null
|
||||||
|
public _queueJob: ((fn: () => void) => void) | null = null
|
||||||
public _revokeProxy: () => void
|
public _revokeProxy: () => void
|
||||||
public _isVue: boolean = true
|
public _isVue: boolean = true
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { MountedComponent } from './component'
|
import { MountedComponent } from './component'
|
||||||
import { ComponentWatchOptions } from './componentOptions'
|
import { ComponentWatchOptions } from './componentOptions'
|
||||||
import { autorun, stop, Autorun } from '@vue/observer'
|
import { autorun, stop } from '@vue/observer'
|
||||||
|
|
||||||
export function initializeWatch(
|
export function initializeWatch(
|
||||||
instance: MountedComponent,
|
instance: MountedComponent,
|
||||||
@ -14,6 +14,7 @@ export function initializeWatch(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO deep watch
|
// TODO deep watch
|
||||||
|
// TODO sync watch
|
||||||
export function setupWatcher(
|
export function setupWatcher(
|
||||||
instance: MountedComponent,
|
instance: MountedComponent,
|
||||||
keyOrFn: string | Function,
|
keyOrFn: string | Function,
|
||||||
@ -21,22 +22,32 @@ export function setupWatcher(
|
|||||||
): () => void {
|
): () => void {
|
||||||
const handles = instance._watchHandles || (instance._watchHandles = new Set())
|
const handles = instance._watchHandles || (instance._watchHandles = new Set())
|
||||||
const proxy = instance.$proxy
|
const proxy = instance.$proxy
|
||||||
|
|
||||||
const rawGetter =
|
const rawGetter =
|
||||||
typeof keyOrFn === 'string'
|
typeof keyOrFn === 'string'
|
||||||
? () => proxy[keyOrFn]
|
? () => proxy[keyOrFn]
|
||||||
: () => keyOrFn.call(proxy)
|
: () => keyOrFn.call(proxy)
|
||||||
|
|
||||||
let oldValue: any
|
let oldValue: any
|
||||||
|
|
||||||
|
const applyCb = () => {
|
||||||
|
const newValue = runner()
|
||||||
|
if (newValue !== oldValue) {
|
||||||
|
cb(newValue, oldValue)
|
||||||
|
oldValue = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const runner = autorun(rawGetter, {
|
const runner = autorun(rawGetter, {
|
||||||
scheduler: (runner: Autorun) => {
|
scheduler: () => {
|
||||||
const newValue = runner()
|
// defer watch callback using the scheduler injected defer.
|
||||||
if (newValue !== oldValue) {
|
instance._queueJob(applyCb)
|
||||||
cb(newValue, oldValue)
|
|
||||||
oldValue = newValue
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
oldValue = runner()
|
oldValue = runner()
|
||||||
handles.add(runner)
|
handles.add(runner)
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
stop(runner)
|
stop(runner)
|
||||||
handles.delete(runner)
|
handles.delete(runner)
|
||||||
|
@ -56,7 +56,10 @@ interface PatchDataFunction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface RendererOptions {
|
interface RendererOptions {
|
||||||
queueJob: (fn: () => void, postFlushJob?: () => void) => void
|
scheduler: {
|
||||||
|
nextTick: (fn: () => void) => Promise<any>
|
||||||
|
queueJob: (fn: () => void, postFlushJob?: () => void) => void
|
||||||
|
}
|
||||||
nodeOps: NodeOps
|
nodeOps: NodeOps
|
||||||
patchData: PatchDataFunction
|
patchData: PatchDataFunction
|
||||||
teardownVNode?: (vnode: VNode) => void
|
teardownVNode?: (vnode: VNode) => void
|
||||||
@ -68,7 +71,7 @@ interface RendererOptions {
|
|||||||
// renderer alongside an actual renderer.
|
// renderer alongside an actual renderer.
|
||||||
export function createRenderer(options: RendererOptions) {
|
export function createRenderer(options: RendererOptions) {
|
||||||
const {
|
const {
|
||||||
queueJob,
|
scheduler: { queueJob, nextTick },
|
||||||
nodeOps: {
|
nodeOps: {
|
||||||
createElement: platformCreateElement,
|
createElement: platformCreateElement,
|
||||||
createText: platformCreateText,
|
createText: platformCreateText,
|
||||||
@ -1186,6 +1189,10 @@ export function createRenderer(options: RendererOptions) {
|
|||||||
(__COMPAT__ && (parentVNode.children as MountedComponent)) ||
|
(__COMPAT__ && (parentVNode.children as MountedComponent)) ||
|
||||||
createComponentInstance(parentVNode, Component, parentComponent)
|
createComponentInstance(parentVNode, Component, parentComponent)
|
||||||
|
|
||||||
|
// renderer-injected scheduler methods
|
||||||
|
instance.$nextTick = nextTick
|
||||||
|
instance._queueJob = queueJob
|
||||||
|
|
||||||
const queueUpdate = (instance.$forceUpdate = () => {
|
const queueUpdate = (instance.$forceUpdate = () => {
|
||||||
queueJob(instance._updateHandle, flushHooks)
|
queueJob(instance._updateHandle, flushHooks)
|
||||||
})
|
})
|
||||||
|
@ -1,12 +1,15 @@
|
|||||||
import { createRenderer, VNode } from '@vue/core'
|
import { createRenderer, VNode } from '@vue/core'
|
||||||
import { queueJob } from '@vue/scheduler'
|
import { queueJob, nextTick } from '@vue/scheduler'
|
||||||
|
|
||||||
import { nodeOps } from './nodeOps'
|
import { nodeOps } from './nodeOps'
|
||||||
import { patchData } from './patchData'
|
import { patchData } from './patchData'
|
||||||
import { teardownVNode } from './teardownVNode'
|
import { teardownVNode } from './teardownVNode'
|
||||||
|
|
||||||
const { render: _render } = createRenderer({
|
const { render: _render } = createRenderer({
|
||||||
queueJob,
|
scheduler: {
|
||||||
|
queueJob,
|
||||||
|
nextTick
|
||||||
|
},
|
||||||
nodeOps,
|
nodeOps,
|
||||||
patchData,
|
patchData,
|
||||||
teardownVNode
|
teardownVNode
|
||||||
|
@ -17,18 +17,14 @@ export function queueJob(job: () => void, postFlushCb?: () => void) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (postFlushCb) {
|
if (postFlushCb) {
|
||||||
queuePostFlushCb(postFlushCb)
|
postFlushCbs.push(postFlushCb)
|
||||||
}
|
}
|
||||||
if (!flushing) {
|
if (!flushing) {
|
||||||
nextTick(flushJobs)
|
nextTick(flushJobs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function queuePostFlushCb(cb: () => void) {
|
function flushJobs() {
|
||||||
postFlushCbs.push(cb)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function flushJobs() {
|
|
||||||
flushing = true
|
flushing = true
|
||||||
let job
|
let job
|
||||||
while ((job = queue.shift())) {
|
while ((job = queue.shift())) {
|
||||||
|
Loading…
Reference in New Issue
Block a user