refactor: ensure watcher callbacks are deferred

This commit is contained in:
Evan You 2018-09-20 18:57:13 -04:00
parent 9b50a5abb9
commit bf38fea313
5 changed files with 38 additions and 17 deletions

View File

@ -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

View File

@ -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)

View File

@ -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)
}) })

View File

@ -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

View File

@ -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())) {